reakit-system
Version:
Reakit System utils
106 lines (94 loc) • 2.96 kB
text/typescript
import * as React from "react";
import { As, PropsWithAs } from "reakit-utils/types";
import { splitProps } from "reakit-utils/splitProps";
import { shallowEqual } from "reakit-utils/shallowEqual";
import { normalizePropsAreEqual } from "reakit-utils/normalizePropsAreEqual";
import { forwardRef } from "./__utils/forwardRef";
import { useCreateElement as defaultUseCreateElement } from "./useCreateElement";
import { memo } from "./__utils/memo";
type RoleHTMLProps = React.HTMLAttributes<any> &
React.RefAttributes<any> & {
wrapElement?: (element: React.ReactNode) => React.ReactNode;
};
type Hook<O> = {
(options?: O, props?: RoleHTMLProps): RoleHTMLProps;
unstable_propsAreEqual?: (prev: O, next: O) => boolean;
__keys?: ReadonlyArray<any>;
};
type Options<T extends As, O> = {
as: T;
useHook?: Hook<O>;
keys?: ReadonlyArray<any>;
memo?: boolean;
propsAreEqual?: (prev: O, next: O) => boolean;
useCreateElement?: (
type: T,
props: Omit<PropsWithAs<O, T>, "as">,
children?: React.ReactNode
) => JSX.Element;
};
export type Component<T extends As, O> = {
<TT extends As>(props: PropsWithAs<O, TT> & { as: TT }): JSX.Element;
(props: PropsWithAs<O, T>): JSX.Element;
displayName?: string;
unstable_propsAreEqual: (
prev: PropsWithAs<O, T>,
next: PropsWithAs<O, T>
) => boolean;
__keys?: ReadonlyArray<any>;
};
/**
* Creates a React component.
*
* @example
* import { createComponent } from "reakit-system";
*
* const A = createComponent({ as: "a" });
*
* @param options
*/
export function createComponent<T extends As, O>({
as: type,
useHook,
memo: shouldMemo,
propsAreEqual = useHook?.unstable_propsAreEqual,
keys = useHook?.__keys || [],
useCreateElement = defaultUseCreateElement,
}: Options<T, O>) {
let Comp = ((
{ as = type, ...props }: PropsWithAs<O, T>,
ref: React.Ref<T>
) => {
if (useHook) {
const [options, htmlProps] = splitProps(props, keys);
const { wrapElement, ...elementProps } = useHook(options, {
ref,
...htmlProps,
});
// @ts-ignore
const asKeys = as.render?.__keys || as.__keys;
const asOptions = asKeys && splitProps(props, asKeys)[0];
const allProps = asOptions
? { ...elementProps, ...asOptions }
: elementProps;
const element = useCreateElement(as, allProps as typeof props);
if (wrapElement) {
return wrapElement(element);
}
return element;
}
return useCreateElement(as, { ref, ...props });
}) as Component<T, O>;
if (process.env.NODE_ENV !== "production" && useHook) {
Comp.displayName = useHook.name.replace(/^(unstable_)?use/, "");
}
Comp = forwardRef(Comp);
if (shouldMemo) {
Comp = memo(Comp, propsAreEqual && normalizePropsAreEqual(propsAreEqual));
}
Comp.__keys = keys;
Comp.unstable_propsAreEqual = normalizePropsAreEqual(
propsAreEqual || shallowEqual
);
return Comp;
}