Skip to content

Commit

Permalink
components: Allow for non-polymorphic components
Browse files Browse the repository at this point in the history
  • Loading branch information
sarayourfriend committed Jun 21, 2021
1 parent 4f9097d commit 19869e8
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 29 deletions.
2 changes: 1 addition & 1 deletion packages/components/src/divider/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface DividerProps extends Omit< SeparatorProps, 'children' > {
}

function Divider(
props: PolymorphicComponentProps< DividerProps, 'hr' >,
props: PolymorphicComponentProps< DividerProps, 'hr', false >,
forwardedRef: Ref< any >
) {
const {
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/ui/context/context-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import { getStyledClassNameFromKey } from './get-styled-class-name-from-key';
* The hope is that we can improve render performance by removing functional
* component wrappers.
*
* @template {import('./polymorphic-component').PolymorphicComponentProps<{}, any>} P
* @template {import('./polymorphic-component').PolymorphicComponentProps<{}, any, any>} P
* @param {(props: P, ref: import('react').Ref<any>) => JSX.Element | null} Component The component to register into the Context system.
* @param {string} namespace The namespace to register the component under.
* @param {Object} options
* @param {boolean} [options.memo=false]
* @return {import('./polymorphic-component').PolymorphicComponent<import('./polymorphic-component').ElementTypeFromPolymorphicComponentProps<P>, import('./polymorphic-component').PropsFromPolymorphicComponentProps<P>>} The connected PolymorphicComponent
* @return {import('./polymorphic-component').PolymorphicComponentFromProps<P>} The connected PolymorphicComponent
*/
export function contextConnect( Component, namespace, options = {} ) {
/* eslint-enable jsdoc/valid-types */
Expand Down
50 changes: 35 additions & 15 deletions packages/components/src/ui/context/polymorphic-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,38 @@ import type * as React from 'react';
* by `ComponentPropsWithRef`. The context is that components should require the `children`
* prop explicitely when needed (see https://github.com/WordPress/gutenberg/pull/31817).
*/
export type PolymorphicComponentProps< P, T extends React.ElementType > = P &
Omit< React.ComponentPropsWithRef< T >, 'as' | keyof P | 'children' > & {
as?: T | keyof JSX.IntrinsicElements;
};
export type PolymorphicComponentProps<
P,
T extends React.ElementType,
IsPolymorphic extends boolean = true
> = P &
Omit< React.ComponentPropsWithRef< T >, 'as' | keyof P | 'children' > &
( IsPolymorphic extends true
? {
as?: T | keyof JSX.IntrinsicElements;
}
: { as?: never } );

export type ElementTypeFromPolymorphicComponentProps<
P
> = P extends PolymorphicComponentProps< unknown, infer T > ? T : never;

export type PropsFromPolymorphicComponentProps<
P
> = P extends PolymorphicComponentProps< infer PP, any > ? PP : never;

export type PolymorphicComponent< T extends React.ElementType, O > = {
export type PolymorphicComponent<
T extends React.ElementType,
O,
IsPolymorphic extends boolean
> = {
< TT extends React.ElementType >(
props: PolymorphicComponentProps< O, TT > & { as: TT }
props: PolymorphicComponentProps<
O,
TT,
IsPolymorphic extends true ? true : false
> &
( IsPolymorphic extends true ? { as: TT } : { as: never } )
): JSX.Element | null;
(
props: PolymorphicComponentProps<
O,
T,
IsPolymorphic extends true ? true : false
>
): JSX.Element | null;
( props: PolymorphicComponentProps< O, T > ): JSX.Element | null;
displayName?: string;
/**
* A CSS selector used to fake component interpolation in styled components
Expand All @@ -40,3 +54,9 @@ export type PolymorphicComponent< T extends React.ElementType, O > = {
*/
selector: `.${ string }`;
};

export type PolymorphicComponentFromProps<
Props
> = Props extends PolymorphicComponentProps< infer P, infer T, infer I >
? PolymorphicComponent< T, P, I >
: never;
7 changes: 6 additions & 1 deletion packages/components/src/ui/popover/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,9 @@ function PopoverContent( props, forwardedRef ) {
);
}

export default contextConnect( PopoverContent, 'PopoverContent' );
const ConnectedPopoverContent = contextConnect(
PopoverContent,
'PopoverContent'
);

export default ConnectedPopoverContent;
13 changes: 4 additions & 9 deletions packages/components/src/ui/utils/create-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import type { As } from 'reakit-utils/types';
import { contextConnect } from '../context';
// eslint-disable-next-line no-duplicate-imports
import type {
PolymorphicComponent,
PropsFromPolymorphicComponentProps,
ElementTypeFromPolymorphicComponentProps,
PolymorphicComponentProps,
PolymorphicComponentFromProps,
} from '../context';
import { View } from '../../view';

interface Options<
A extends As,
P extends PolymorphicComponentProps< {}, A >
P extends PolymorphicComponentProps< {}, A, any >
> {
as: A;
name: string;
Expand All @@ -40,16 +38,13 @@ interface Options<
*/
export const createComponent = <
A extends As,
P extends PolymorphicComponentProps< {}, A >
P extends PolymorphicComponentProps< {}, A, any >
>( {
as,
name,
useHook,
memo = false,
}: Options< A, P > ): PolymorphicComponent<
ElementTypeFromPolymorphicComponentProps< P >,
PropsFromPolymorphicComponentProps< P >
> => {
}: Options< A, P > ): PolymorphicComponentFromProps< P > => {
function Component( props: P, forwardedRef: Ref< any > ) {
const otherProps = useHook( props );

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/view/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import styled from '@emotion/styled';
* }
* ```
*
* @type {import('../ui/context').PolymorphicComponent<'div', { children?: import('react').ReactNode }>}
* @type {import('../ui/context').PolymorphicComponent<'div', { children?: import('react').ReactNode }, true>}
*/
// @ts-ignore
const View = styled.div``;
Expand Down

0 comments on commit 19869e8

Please sign in to comment.