I'm trying to implement a custom label component with Typescript, building on the <Label /> component that comes with theme-ui. My first attempt looks something like this:
export const CustomLabel = React.forwardRef<HTMLLabelElement, LabelProps>(
(props , ref) => <Label ref={ref} {...props} />
);
I would've expected this to work, but instead I get the following error on ref={ref}:
Type 'string | ((instance: HTMLLabelElement | null) => void) | RefObject<HTMLLabelElement> | null' is not assignable to type '((instance: HTMLLabelElement | null) => void) | RefObject<HTMLLabelElement> | null | undefined'.
Type 'string' is not assignable to type '((instance: HTMLLabelElement | null) => void) | RefObject<HTMLLabelElement> | null | undefined'.
Am I doing something very silly here? Has anyone else run into a similar issue?
I have similar issues.
I fixed the type issues by unshamelessly stealing the Assign and ForwardRef types from the @theme-ui/components conversion to TypeScript PR
(I am not good enough with TypeScript to understand them fully, so copypasting them makes me feel even worse but... At least it seems to work 馃檲)
import React from 'react';
import { Text, TextProps } from '@theme-ui/components';
type Assign<T, U> = {
[P in keyof (T & U)]: P extends keyof T
? T[P]
: P extends keyof U
? U[P]
: never
}
type ForwardRef<T, P> =
React.ForwardRefExoticComponent<
React.PropsWithoutRef<P>& React.RefAttributes<T>
>
export interface ParagraphProps
extends Assign<React.ComponentPropsWithRef<'p'>, TextProps> {};
export const Paragraph: ForwardRef<HTMLParagraphElement, ParagraphProps> =
React.forwardRef((props, ref) => (
<Text
as="p"
{...props}
ref={ref}
/>
));
But I am facing various type issues as soon as I want to use variants, themeKey and sx props.
I would love to understand that and help documenting eventually?
More specifically by "various issues": I am using Storybook to document components, so my issue may be related to that, but it's hard to figure out at the moment.
I use a "decorator" to wrap all components under a <ThemeProvider /> with the following theme:
import { merge } from 'theme-ui';
import { base } from '@theme-ui/presets';
export const theme = merge(base, {
colors: {
primary: '#3d5a80'
},
text: {
primary: {
color: 'primary',
textTransform: 'uppercase'
}
}
});
Then my story is:
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { Text } from 'theme-ui';
import { Paragraph } from './Paragraph.tsx';
# Text
<Meta title="Typography/Text" component={Text} />
<Preview>
<Story name="Text">
<Text as="p" variant="primary">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</Text>
</Story>
</Preview>
<Preview>
<Story name="Paragraph">
<Paragraph variant="primary">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</Paragraph>
</Story>
</Preview>
The first one renders correctly, the second does not.
However, that component in a codesandbox works fine:
https://codesandbox.io/s/theme-ui-components-uozex
Just wondering if there are specific MDX or props forwarding requirements to use the theme...?
@calvinwyoung the types are incorrect in Theme UI. They are largely missing ComponentPropsWithRef. I have created #1213 to address the issue.
As a temporary workaround, I created a type helper that I have been using:
type BoxPropsWithRef<
T extends React.ElementType,
P extends BoxOwnProps = BoxOwnProps
> = React.ComponentPropsWithRef<T> & P
I can then type my custom override component:
import React, { forwardRef } from 'react'
import { Label as ThemeUILabel, LabelProps as ThemeUILabelProps } from 'theme-ui'
export type LabelProps = BoxPropsWithRef<'label', ThemeUILabelProps>
export const Label = forwardRef<HTMLLabelElement, LabelProps>(
(props, ref) => <ThemeUILabel ref={ref} {...props} />
)
Most helpful comment
@calvinwyoung the types are incorrect in Theme UI. They are largely missing
ComponentPropsWithRef. I have created #1213 to address the issue.As a temporary workaround, I created a type helper that I have been using:
I can then type my custom override component: