diff --git a/src/index.tsx b/src/index.tsx index 2abb302..2b70e60 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,10 +2,62 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import makeCancellable from 'make-cancellable-promise'; +// Source: https://github.com/emotion-js/emotion/blob/master/packages/styled-base/types/helper.d.ts +// A more precise version of just React.ComponentPropsWithoutRef on its own +export type PropsOf> = + JSX.LibraryManagedAttributes>; + +type AsProp = { + /** + * An override of the default HTML tag. + * Can also be another React component. + */ + as?: C; +}; + +/** + * Allows for extending a set of props (`ExtendedProps`) by an overriding set of props + * (`OverrideProps`), ensuring that any duplicates are overridden by the overriding + * set of props. + */ +export type ExtendableProps = OverrideProps & + Omit; + +/** + * Allows for inheriting the props from the specified element type so that + * props like children, className & style work, as well as element-specific + * attributes like aria roles. The component (`C`) must be passed in. + */ +export type InheritableElementProps = ExtendableProps< + PropsOf, + Props +>; + +/** + * A more sophisticated version of `InheritableElementProps` where + * the passed in `as` prop will determine which props can be included + */ +export type PolymorphicComponentProps< + C extends React.ElementType, + Props = unknown, +> = InheritableElementProps>; + +/** + * Utility type to extract the `ref` prop from a polymorphic component + */ +export type PolymorphicRef = React.ComponentPropsWithRef['ref']; +/** + * A wrapper of `PolymorphicComponentProps` that also includes the `ref` + * prop for the polymorphic component + */ +export type PolymorphicComponentPropsWithRef< + C extends React.ElementType, + Props = unknown, +> = PolymorphicComponentProps & { ref?: PolymorphicRef }; + type Config = React.ComponentPropsWithoutRef; -type AsyncButtonProps = { - as?: T; +type Props = { errorConfig?: Config; onClick?: (event: React.MouseEvent) => void | Promise; pendingConfig?: Config; @@ -13,6 +65,11 @@ type AsyncButtonProps = { successConfig?: Config; } & Config; +type AsyncButtonProps = PolymorphicComponentPropsWithRef< + C, + Props +>; + const STATES = { ERROR: 'error', INIT: 'init', @@ -31,7 +88,7 @@ const AsyncButton = React.forwardRef( successConfig, ...otherProps }: AsyncButtonProps, - ref: React.ForwardedRef, + ref?: PolymorphicRef, ) => { const [buttonState, setButtonState] = useState(STATES.INIT); const cancellablePromise = useRef>(); @@ -135,6 +192,6 @@ AsyncButton.propTypes = { successConfig: isConfigObject, }; -export default AsyncButton as ( - props: AsyncButtonProps & React.RefAttributes, -) => React.ReactElement | null; +export default AsyncButton as ( + props: AsyncButtonProps & { ref?: PolymorphicRef }, +) => React.ReactElement;