|
1 | | -import React, { |
2 | | - useId, |
3 | | - memo, |
4 | | - forwardRef, |
5 | | - type ReactNode, |
6 | | - type CSSProperties, |
7 | | - type InputHTMLAttributes, |
8 | | - type DetailedHTMLProps |
9 | | -} from "react"; |
| 1 | +import React, { memo, forwardRef } from "react"; |
10 | 2 | import { symToStr } from "tsafe/symToStr"; |
11 | | -import { assert } from "tsafe/assert"; |
12 | | -import type { Equals } from "tsafe"; |
13 | | -import { cx } from "./tools/cx"; |
14 | | -import { fr } from "./fr"; |
| 3 | +import { Fieldset, type FieldsetProps } from "./shared/Fieldset"; |
15 | 4 |
|
16 | | -export type RadioButtonsProps = { |
17 | | - className?: string; |
18 | | - classes?: Partial<Record<"root" | "legend" | "content", string>>; |
19 | | - style?: CSSProperties; |
20 | | - legend: ReactNode; |
21 | | - hintText?: ReactNode; |
22 | | - name?: string; |
23 | | - options: { |
24 | | - label: ReactNode; |
25 | | - hintText?: ReactNode; |
26 | | - nativeInputProps: DetailedHTMLProps< |
27 | | - InputHTMLAttributes<HTMLInputElement>, |
28 | | - HTMLInputElement |
29 | | - >; |
30 | | - }[]; |
31 | | - /** Default: "vertical" */ |
32 | | - orientation?: "vertical" | "horizontal"; |
33 | | - /** Default: "default" */ |
34 | | - state?: "success" | "error" | "default"; |
35 | | - /** |
36 | | - * The message won't be displayed if state is "default". |
37 | | - * If the state is "error" providing a message is mandatory |
38 | | - **/ |
39 | | - stateRelatedMessage?: ReactNode; |
40 | | - /** Default: false */ |
41 | | - disabled?: boolean; |
42 | | -}; |
| 5 | +export type RadioButtonsProps = FieldsetProps.Common & { name?: string }; |
43 | 6 |
|
44 | 7 | /** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-radiobutton> */ |
45 | 8 | export const RadioButtons = memo( |
46 | | - forwardRef<HTMLFieldSetElement, RadioButtonsProps>((props, ref) => { |
47 | | - const { |
48 | | - className, |
49 | | - classes = {}, |
50 | | - style, |
51 | | - name: name_props, |
52 | | - legend, |
53 | | - hintText, |
54 | | - options, |
55 | | - orientation = "vertical", |
56 | | - state = "default", |
57 | | - stateRelatedMessage, |
58 | | - disabled = false, |
59 | | - ...rest |
60 | | - } = props; |
61 | | - |
62 | | - assert<Equals<keyof typeof rest, never>>(); |
63 | | - |
64 | | - const { getRadioId, legendId, errorDescId, successDescId } = (function useClosure() { |
65 | | - const id = `radio${name_props === undefined ? "" : `-${name_props}`}-${useId()}`; |
66 | | - |
67 | | - const getRadioId = (i: number) => `${id}-${i}`; |
68 | | - |
69 | | - const legendId = `${id}-legend`; |
70 | | - |
71 | | - const errorDescId = `${id}-desc-error`; |
72 | | - const successDescId = `${id}-desc-valid`; |
73 | | - |
74 | | - return { getRadioId, legendId, errorDescId, successDescId }; |
75 | | - })(); |
76 | | - |
77 | | - const name = (function useClosure() { |
78 | | - const id = useId(); |
79 | | - |
80 | | - return name_props ?? `radio-name-${id}`; |
81 | | - })(); |
82 | | - |
83 | | - return ( |
84 | | - <fieldset |
85 | | - className={cx( |
86 | | - fr.cx( |
87 | | - "fr-fieldset", |
88 | | - orientation === "horizontal" && "fr-fieldset--inline", |
89 | | - (() => { |
90 | | - switch (state) { |
91 | | - case "default": |
92 | | - return undefined; |
93 | | - case "error": |
94 | | - return "fr-fieldset--error"; |
95 | | - case "success": |
96 | | - return "fr-fieldset--valid"; |
97 | | - } |
98 | | - })() |
99 | | - ), |
100 | | - classes.root, |
101 | | - className |
102 | | - )} |
103 | | - disabled={disabled} |
104 | | - style={style} |
105 | | - aria-labelledby={cx( |
106 | | - legendId, |
107 | | - (() => { |
108 | | - switch (state) { |
109 | | - case "default": |
110 | | - return undefined; |
111 | | - case "error": |
112 | | - return errorDescId; |
113 | | - case "success": |
114 | | - return successDescId; |
115 | | - } |
116 | | - })() |
117 | | - )} |
118 | | - role={state === "default" ? undefined : "group"} |
119 | | - {...rest} |
120 | | - ref={ref} |
121 | | - > |
122 | | - <legend |
123 | | - id={legendId} |
124 | | - className={cx(fr.cx("fr-fieldset__legend", "fr-text--regular"), classes.legend)} |
125 | | - > |
126 | | - {legend} |
127 | | - {hintText !== undefined && ( |
128 | | - <span className={fr.cx("fr-hint-text")}>{hintText}</span> |
129 | | - )} |
130 | | - </legend> |
131 | | - <div className={cx(fr.cx("fr-fieldset__content"), classes.content)}> |
132 | | - {options.map(({ label, hintText, nativeInputProps }, i) => ( |
133 | | - <div className={fr.cx("fr-radio-group")} key={i}> |
134 | | - <input |
135 | | - type="radio" |
136 | | - id={getRadioId(i)} |
137 | | - name={name} |
138 | | - {...nativeInputProps} |
139 | | - /> |
140 | | - <label className={fr.cx("fr-label")} htmlFor={getRadioId(i)}> |
141 | | - {label} |
142 | | - {hintText !== undefined && ( |
143 | | - <span className={fr.cx("fr-hint-text")}>{hintText}</span> |
144 | | - )} |
145 | | - </label> |
146 | | - </div> |
147 | | - ))} |
148 | | - </div> |
149 | | - {state !== "default" && ( |
150 | | - <p |
151 | | - id={(() => { |
152 | | - switch (state) { |
153 | | - case "error": |
154 | | - return errorDescId; |
155 | | - case "success": |
156 | | - return successDescId; |
157 | | - } |
158 | | - })()} |
159 | | - className={fr.cx( |
160 | | - (() => { |
161 | | - switch (state) { |
162 | | - case "error": |
163 | | - return "fr-error-text"; |
164 | | - case "success": |
165 | | - return "fr-valid-text"; |
166 | | - } |
167 | | - })() |
168 | | - )} |
169 | | - > |
170 | | - {stateRelatedMessage ?? ""} |
171 | | - </p> |
172 | | - )} |
173 | | - </fieldset> |
174 | | - ); |
175 | | - }) |
| 9 | + forwardRef<HTMLFieldSetElement, RadioButtonsProps>((props, ref) => ( |
| 10 | + <Fieldset ref={ref} type="radio" {...props} /> |
| 11 | + )) |
176 | 12 | ); |
177 | 13 |
|
178 | 14 | RadioButtons.displayName = symToStr({ RadioButtons }); |
|
0 commit comments