Polymorphic Components with Typescript
Modified 2022-09-23
type VariantTypeStyles = keyof DefaultTheme['typography']
interface ITypographyProps<HTMLTag extends React.ElementType = React.ElementType>
extends IDOMProps,
IStyleProps {
/**
* Applies the theme typography styles.
* @default 'body'
*/
variant?: VariantTypeStyles
/**
* Change the semantic HTML element, while visually maintaining the style
* of the variant
*/
component?: HTMLTag
/** The content of what needs to be marked up. */
children: ReactNode
}
type TypographyProps<HTMLTag extends React.ElementType> = ITypographyProps<HTMLTag> &
Omit<React.ComponentPropsWithoutRef<HTMLTag>, keyof ITypographyProps>
const defaultElement = 'span'
const variantMap = new Map<VariantTypeStyles, React.ElementType>([
['h1', 'h1'],
['h2', 'h2'],
['h3', 'h3'],
['subtitle', 'h6'],
['body', 'p'],
['strong', 'strong'],
['button', 'span'],
['pretitle', 'span'],
['small', 'span'],
])
/**
* The Typography component is used to quickly apply a default set of font
* styles in the application in order to keep the typography consistent.
*
* NOTE: It's completely okay to semantically need a different HTML element,
* than what is by default used mapping from the variants. It's encouraged even!
*/
export function Typography<HTMLTag extends React.ElementType = typeof defaultElement>({
variant = 'body',
component,
children,
...props
}: TypographyProps<HTMLTag>) {
return (
<StyledTypography
variant={variant}
as={component || (variantMap.get(variant) ?? defaultElement)}
{...props}
>
{children}
</StyledTypography>
)
}