Skip to content

Commit 13abe06

Browse files
authored
Merge pull request #211 from codegouvfr/feature/radio-rich-buttons
Feature / RadioRichButtons
2 parents 594e1b8 + ab1371c commit 13abe06

File tree

6 files changed

+206
-13
lines changed

6 files changed

+206
-13
lines changed

src/Checkbox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import React, { memo, forwardRef } from "react";
22
import { symToStr } from "tsafe/symToStr";
33
import { Fieldset, type FieldsetProps } from "./shared/Fieldset";
44

5-
export type CheckboxProps = FieldsetProps.Common;
5+
export type CheckboxProps = Omit<FieldsetProps.Checkbox, "type">;
66

77
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-checkbox> */
88
export const Checkbox = memo(
99
forwardRef<HTMLFieldSetElement, CheckboxProps>((props, ref) => (
10-
<Fieldset ref={ref} type="checkbox" {...props} />
10+
<Fieldset ref={ref} {...props} type="checkbox" />
1111
))
1212
);
1313

src/RadioButtons.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import React, { memo, forwardRef } from "react";
22
import { symToStr } from "tsafe/symToStr";
33
import { Fieldset, type FieldsetProps } from "./shared/Fieldset";
44

5-
export type RadioButtonsProps = FieldsetProps.Common & { name?: string };
5+
export type RadioButtonsProps = Omit<FieldsetProps.Radio, "type">;
66

77
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-radiobutton> */
88
export const RadioButtons = memo(
99
forwardRef<HTMLFieldSetElement, RadioButtonsProps>((props, ref) => (
10-
<Fieldset ref={ref} type="radio" {...props} />
10+
<Fieldset ref={ref} {...props} type="radio" />
1111
))
1212
);
1313

src/shared/Fieldset.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import React, {
44
forwardRef,
55
type ReactNode,
66
type CSSProperties,
7-
type InputHTMLAttributes,
8-
type DetailedHTMLProps
7+
type ComponentProps
98
} from "react";
109
import { symToStr } from "tsafe/symToStr";
1110
import { assert } from "tsafe/assert";
@@ -27,11 +26,9 @@ export namespace FieldsetProps {
2726
options: {
2827
label: ReactNode;
2928
hintText?: ReactNode;
30-
nativeInputProps: DetailedHTMLProps<
31-
InputHTMLAttributes<HTMLInputElement>,
32-
HTMLInputElement
33-
>;
29+
nativeInputProps: ComponentProps<"input">;
3430
}[];
31+
3532
/** Default: "vertical" */
3633
orientation?: "vertical" | "horizontal";
3734
/** Default: "default" */
@@ -47,9 +44,12 @@ export namespace FieldsetProps {
4744
small?: boolean;
4845
};
4946

50-
export type Radio = Common & {
47+
export type Radio = Omit<Common, "options"> & {
5148
type: "radio";
5249
name?: string;
50+
options: (Common["options"][number] & {
51+
illustration?: ReactNode;
52+
})[];
5353
};
5454

5555
export type Checkbox = Common & {
@@ -79,6 +79,10 @@ export const Fieldset = memo(
7979
...rest
8080
} = props;
8181

82+
const isRichRadio =
83+
type === "radio" &&
84+
options.find(options => options.illustration !== undefined) !== undefined;
85+
8286
assert<Equals<keyof typeof rest, never>>();
8387

8488
const id = useAnalyticsId({
@@ -145,9 +149,13 @@ export const Fieldset = memo(
145149
</legend>
146150
)}
147151
<div className={cx(fr.cx("fr-fieldset__content"), classes.content)}>
148-
{options.map(({ label, hintText, nativeInputProps }, i) => (
152+
{options.map(({ label, hintText, nativeInputProps, ...rest }, i) => (
149153
<div
150-
className={fr.cx(`fr-${type}-group`, small && `fr-${type}-group--sm`)}
154+
className={fr.cx(
155+
`fr-${type}-group`,
156+
isRichRadio && "fr-radio-rich",
157+
small && `fr-${type}-group--sm`
158+
)}
151159
key={i}
152160
>
153161
<input
@@ -162,6 +170,11 @@ export const Fieldset = memo(
162170
<span className={fr.cx("fr-hint-text")}>{hintText}</span>
163171
)}
164172
</label>
173+
{"illustration" in rest && (
174+
<div className={fr.cx("fr-radio-rich__img")}>
175+
{rest.illustration}
176+
</div>
177+
)}
165178
</div>
166179
))}
167180
</div>

stories/RadioButtons.stories.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from "react";
12
import { RadioButtons, type RadioButtonsProps } from "../dist/RadioButtons";
23
import { sectionName } from "./sectionName";
34
import { getStoryFactory } from "./getStory";
@@ -384,3 +385,33 @@ export const Small = getStory({
384385
}
385386
]
386387
});
388+
389+
export const Rich = getStory({
390+
"legend": "Légende pour l’ensemble de champs",
391+
"name": "radio",
392+
"options": [
393+
{
394+
"label": "Label radio",
395+
"nativeInputProps": {
396+
"value": "value1"
397+
},
398+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
399+
},
400+
{
401+
"label": "Label radio 2",
402+
"nativeInputProps": {
403+
"value": "value2"
404+
},
405+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
406+
},
407+
{
408+
"label": "Label radio 3",
409+
"nativeInputProps": {
410+
"value": "value3"
411+
},
412+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
413+
}
414+
],
415+
"state": "default",
416+
"stateRelatedMessage": "State description"
417+
});

test/types/Checkbox.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from "react";
2+
import { Checkbox } from "../../src/Checkbox";
3+
4+
{
5+
<Checkbox
6+
legend="Label"
7+
options={[
8+
{
9+
"label": "Label 1",
10+
"hintText": "Hint text",
11+
"nativeInputProps": {
12+
"value": 1
13+
}
14+
},
15+
{
16+
"label": "Label 2",
17+
"hintText": "Hint text",
18+
"nativeInputProps": {
19+
"value": 2
20+
}
21+
}
22+
]}
23+
/>;
24+
}
25+
{
26+
<Checkbox
27+
orientation="horizontal"
28+
className={"fr-mt-1w"}
29+
options={[
30+
{
31+
"label": "Label 1",
32+
"hintText": "Hint text",
33+
"nativeInputProps": {
34+
"value": 1
35+
}
36+
},
37+
{
38+
"label": "Label 2",
39+
"hintText": "Hint text",
40+
"nativeInputProps": {
41+
"value": 2
42+
},
43+
// @ts-expect-error
44+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
45+
}
46+
]}
47+
/>;
48+
}

test/types/RadioButtons.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from "react";
2+
import { RadioButtons } from "../../src/RadioButtons";
3+
import { RadioRichButtons } from "../../src/RadioRichButtons";
4+
5+
{
6+
<RadioButtons
7+
legend="Label"
8+
name="name"
9+
options={[
10+
{
11+
"label": "Label 1",
12+
"hintText": "Hint text",
13+
"nativeInputProps": {
14+
"value": 1
15+
}
16+
},
17+
{
18+
"label": "Label 2",
19+
"hintText": "Hint text",
20+
"nativeInputProps": {
21+
"value": 2
22+
}
23+
}
24+
]}
25+
/>;
26+
}
27+
28+
{
29+
<RadioRichButtons
30+
legend="Label"
31+
name="name"
32+
options={[
33+
{
34+
"label": "Label 1",
35+
"hintText": "Hint text",
36+
"nativeInputProps": {
37+
"value": 1
38+
},
39+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
40+
},
41+
{
42+
"label": "Label 2",
43+
"hintText": "Hint text",
44+
"nativeInputProps": {
45+
"value": 2
46+
},
47+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
48+
}
49+
]}
50+
/>;
51+
}
52+
53+
{
54+
<RadioButtons
55+
legend="Label"
56+
name="name"
57+
options={[
58+
{
59+
"label": "Label 1",
60+
"hintText": "Hint text",
61+
"nativeInputProps": {
62+
"value": 1
63+
},
64+
// @ts-expect-error
65+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
66+
},
67+
{
68+
"label": "Label 2",
69+
"hintText": "Hint text",
70+
"nativeInputProps": {
71+
"value": 2
72+
}
73+
}
74+
]}
75+
/>;
76+
}
77+
78+
{
79+
<RadioRichButtons
80+
legend="Label"
81+
name="name"
82+
options={[
83+
{
84+
"label": "Label 1",
85+
"hintText": "Hint text",
86+
"nativeInputProps": {
87+
"value": 1
88+
},
89+
"illustration": <img src="https://placehold.it/100x100" alt="illustration" />
90+
},
91+
// @ts-expect-error
92+
{
93+
"label": "Label 2",
94+
"hintText": "Hint text",
95+
"nativeInputProps": {
96+
"value": 2
97+
}
98+
}
99+
]}
100+
/>;
101+
}

0 commit comments

Comments
 (0)