reakit
Version:
Toolkit for building accessible rich web apps with React
105 lines (92 loc) • 3.17 kB
text/typescript
import * as React from "react";
import { createComponent } from "reakit-system/createComponent";
import { createHook } from "reakit-system/createHook";
import { As, PropsWithAs } from "reakit-utils/types";
import { useLiveRef } from "reakit-utils/useLiveRef";
import { RadioHTMLProps, useRadio } from "../Radio/Radio";
import { RoleOptions } from "../Role";
import { FormRadioGroupContext } from "./FormRadioGroup";
import { unstable_FormStateReturn } from "./FormState";
import { unstable_getIn } from "./utils/getIn";
import { formatInputName } from "./__utils/formatInputName";
import { DeepPath, DeepPathValue } from "./__utils/types";
import { FORM_RADIO_KEYS } from "./__keys";
export type unstable_FormRadioOptions<
V,
P extends DeepPath<V, P>
> = RoleOptions &
Pick<unstable_FormStateReturn<V>, "values" | "update" | "blur"> & {
/**
* FormRadio's name as in form values.
*/
name: P;
/**
* FormRadio's value.
*/
value: DeepPathValue<V, P>;
};
export type unstable_FormRadioHTMLProps = RadioHTMLProps;
export type unstable_FormRadioProps<
V,
P extends DeepPath<V, P>
> = unstable_FormRadioOptions<V, P> & unstable_FormRadioHTMLProps;
export const unstable_useFormRadio = createHook<
unstable_FormRadioOptions<any, any>,
unstable_FormRadioHTMLProps
>({
name: "FormRadio",
compose: useRadio,
keys: FORM_RADIO_KEYS,
useOptions(options, htmlProps) {
const name = options.name || htmlProps.name;
const value =
typeof options.value !== "undefined" ? options.value : htmlProps.value;
const composite = React.useContext(FormRadioGroupContext);
const currentChecked = unstable_getIn(options.values, name);
const checked = currentChecked === value;
if (!composite) {
// TODO: Better error
throw new Error("Missing FormRadioGroup");
}
return { ...options, ...composite, checked, name, value };
},
useProps(
options,
{ onChange: htmlOnChange, onBlur: htmlOnBlur, ...htmlProps }
) {
const onChangeRef = useLiveRef(htmlOnChange);
const onBlurRef = useLiveRef(htmlOnBlur);
const onChange = React.useCallback(
(event: React.ChangeEvent) => {
onChangeRef.current?.(event);
if (event.defaultPrevented) return;
options.update?.(options.name, options.value);
},
[options.update, options.name, options.value]
);
const onBlur = React.useCallback(
(event: React.FocusEvent) => {
onBlurRef.current?.(event);
if (event.defaultPrevented) return;
options.blur?.(options.name);
},
[options.blur, options.name]
);
return {
name: formatInputName(options.name),
onChange,
onBlur,
...htmlProps,
};
},
}) as <V, P extends DeepPath<V, P>>(
options: unstable_FormRadioOptions<V, P>,
htmlProps?: unstable_FormRadioHTMLProps
) => unstable_FormRadioHTMLProps;
export const unstable_FormRadio = (createComponent({
as: "input",
memo: true,
useHook: unstable_useFormRadio,
}) as unknown) as <V, P extends DeepPath<V, P>, T extends As = "input">(
props: PropsWithAs<unstable_FormRadioOptions<V, P>, T>
) => JSX.Element;