Created
May 22, 2025 15:48
-
-
Save moritzsalla/b11947e83afb5cb06c35bf3cba3ef67e to your computer and use it in GitHub Desktop.
Creates a component that renders different variants based on a `type` prop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ComponentMap<Type extends Record<string, unknown>> = { | |
[Key in keyof Type]: React.FC<Type[Key]>; | |
}; | |
type VariantProps<Type extends Record<string, unknown>> = { | |
[Key in keyof Type]: { type: Key } & Type[Key]; | |
}[keyof Type]; | |
type CreateVariantOptions<Type extends Record<string, unknown>> = { | |
render?: ( | |
element: React.ReactElement, | |
props: VariantProps<Type>, | |
) => React.ReactElement; | |
}; | |
/** | |
* Extracts the props type from any React component. | |
* | |
* @example | |
* ```tsx | |
* const MyComponent = createVariantComponent({...}); | |
* type MyComponentProps = InferProps<typeof MyComponent>; | |
* | |
* // Use the extracted type | |
* const wrapper = (props: MyComponentProps) => <MyComponent {...props} />; | |
* ``` | |
*/ | |
// Using any as a constraint here, not as a type. | |
// Unknown is not generic enough. | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
export type ExtractProps<Type extends React.ComponentType<any>> = Pretty< | |
React.ComponentProps<Type> | |
>; | |
/** | |
* Creates a component that renders different variants based on a `type` prop. | |
* | |
* @example | |
* ```tsx | |
* const Card = createVariantComponent({ | |
* basic: BasicCard, // renders <BasicCard {...props} /> | |
* premium: PremiumCard, // renders <PremiumCard {...props} /> | |
* }); | |
* | |
* <Card type="basic" title="Hello" /> | |
* <Card type="premium" title="Hello" features={['a', 'b']} /> | |
* ``` | |
* | |
* @example | |
* | |
* If you need to add a wrapper around the component, you can use the `render` option: | |
* ```tsx | |
* const Card = createVariantComponent({...}, { | |
* render: (element, props) => { | |
* return <div className="wrapper">{element}</div>; | |
* }, | |
* }); | |
*/ | |
export const createVariantComponent = | |
< | |
Type extends Record< | |
string, | |
// Dito. 👆 | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
any | |
>, | |
>( | |
variants: ComponentMap<Type>, | |
options?: CreateVariantOptions<Type>, | |
) => | |
(props: VariantProps<Type>) => { | |
const El = variants[props.type] as React.FC<Type[keyof Type]>; | |
const element = <El {...(props as Type[keyof Type])} />; | |
return options?.render ? options.render(element, props) : element; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment