UNPKG

3.17 kBPlain TextView Raw
1import * as React from "react";
2import { createComponent } from "reakit-system/createComponent";
3import { createHook } from "reakit-system/createHook";
4import { As, PropsWithAs } from "reakit-utils/types";
5import { useLiveRef } from "reakit-utils/useLiveRef";
6import { RadioHTMLProps, useRadio } from "../Radio/Radio";
7import { RoleOptions } from "../Role";
8import { FormRadioGroupContext } from "./FormRadioGroup";
9import { unstable_FormStateReturn } from "./FormState";
10import { unstable_getIn } from "./utils/getIn";
11import { formatInputName } from "./__utils/formatInputName";
12import { DeepPath, DeepPathValue } from "./__utils/types";
13import { FORM_RADIO_KEYS } from "./__keys";
14
15export type unstable_FormRadioOptions<
16 V,
17 P extends DeepPath<V, P>
18> = RoleOptions &
19 Pick<unstable_FormStateReturn<V>, "values" | "update" | "blur"> & {
20 /**
21 * FormRadio's name as in form values.
22 */
23 name: P;
24 /**
25 * FormRadio's value.
26 */
27 value: DeepPathValue<V, P>;
28 };
29
30export type unstable_FormRadioHTMLProps = RadioHTMLProps;
31
32export type unstable_FormRadioProps<
33 V,
34 P extends DeepPath<V, P>
35> = unstable_FormRadioOptions<V, P> & unstable_FormRadioHTMLProps;
36
37export const unstable_useFormRadio = createHook<
38 unstable_FormRadioOptions<any, any>,
39 unstable_FormRadioHTMLProps
40>({
41 name: "FormRadio",
42 compose: useRadio,
43 keys: FORM_RADIO_KEYS,
44
45 useOptions(options, htmlProps) {
46 const name = options.name || htmlProps.name;
47 const value =
48 typeof options.value !== "undefined" ? options.value : htmlProps.value;
49 const composite = React.useContext(FormRadioGroupContext);
50 const currentChecked = unstable_getIn(options.values, name);
51 const checked = currentChecked === value;
52
53 if (!composite) {
54 // TODO: Better error
55 throw new Error("Missing FormRadioGroup");
56 }
57
58 return { ...options, ...composite, checked, name, value };
59 },
60
61 useProps(
62 options,
63 { onChange: htmlOnChange, onBlur: htmlOnBlur, ...htmlProps }
64 ) {
65 const onChangeRef = useLiveRef(htmlOnChange);
66 const onBlurRef = useLiveRef(htmlOnBlur);
67
68 const onChange = React.useCallback(
69 (event: React.ChangeEvent) => {
70 onChangeRef.current?.(event);
71 if (event.defaultPrevented) return;
72 options.update?.(options.name, options.value);
73 },
74 [options.update, options.name, options.value]
75 );
76
77 const onBlur = React.useCallback(
78 (event: React.FocusEvent) => {
79 onBlurRef.current?.(event);
80 if (event.defaultPrevented) return;
81 options.blur?.(options.name);
82 },
83 [options.blur, options.name]
84 );
85
86 return {
87 name: formatInputName(options.name),
88 onChange,
89 onBlur,
90 ...htmlProps,
91 };
92 },
93}) as <V, P extends DeepPath<V, P>>(
94 options: unstable_FormRadioOptions<V, P>,
95 htmlProps?: unstable_FormRadioHTMLProps
96) => unstable_FormRadioHTMLProps;
97
98export const unstable_FormRadio = (createComponent({
99 as: "input",
100 memo: true,
101 useHook: unstable_useFormRadio,
102}) as unknown) as <V, P extends DeepPath<V, P>, T extends As = "input">(
103 props: PropsWithAs<unstable_FormRadioOptions<V, P>, T>
104) => JSX.Element;