Skip to content

Commit 256b260

Browse files
authored
Link in external project (#56)
* Enable the module to be linked in other project * Feature RadioButtons * Bump version
1 parent eef12d9 commit 256b260

File tree

6 files changed

+458
-4
lines changed

6 files changed

+458
-4
lines changed

COMPONENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- [x] Button
99
- [x] Buttons group
1010
- [ ] France Connect button
11-
- [ ] Radio button
11+
- [x] Radio button
1212
- [ ] Radio rich
1313
- [ ] Checkbox
1414
- [x] Cards

README.fr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ DSFR en pur JavaScript/CSS.
4545
- [x] la plupart des composants peuvent être rendus directement sur le serveur (voir [RSC](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html)). Les autres sont étiquetées `"use client";`.
4646
- [x] [Intégration clef en main pour les différents frameworks de développement: vite, Next.js, y compris la version beta de Next 13 (configuration AppDir) et Create React App](https://react-dsfr.etalab.studio/) si votre
4747
framework n'est pas supporter, il suffit de demander notre, nous avons pour but de couvrir tous les cas d'usage effectifs.
48-
- [ ] tout [les composants de référence implémenter](https://www.systeme-de-design.gouv.fr/elements-d-interface). À ce jour 18/41, [see details](COMPONENTS.md)
48+
- [ ] tout [les composants de référence implémenter](https://www.systeme-de-design.gouv.fr/elements-d-interface). À ce jour 19/41, [see details](COMPONENTS.md)
4949
- [x] seulement le code des composants que vous utilisez effectivement sera inclus dans votre projet final.
5050
- [x] Intégration facultative avec [MUI](https://mui.com/). Si vous utilisez des composants MUI ils seront automatiquement adaptés pour ressembler à des composants DSFR.
5151
Voir là [documentation](https://react-dsfr.etalab.studio/mui-integration).

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ This module is a wrapper/compatibility layer for [@gouvfr/dsfr](https://github.c
4545
- [x] No [white flash when reloading in SSR setup](https://github.com/codegouvfr/@codegouvfr/react-dsfr/issues/2#issuecomment-1257263480).
4646
- [x] Most components are server component ready. The others are labeled with `"use client";`
4747
- [x] [Perfect integration with all major React framework: Next.js (PagesDir and AppDir), Create React App, Vue](https://react-dsfr.etalab.studio/).
48-
- [ ] All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are implemented (18/41, [see details](COMPONENTS.md))
48+
- [ ] All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are implemented (19/41, [see details](COMPONENTS.md))
4949
- [x] Three shakable distribution, cherry pick the components you import. (It's not all in a big .js bundle)
5050
- [x] Optional integration with [MUI](https://mui.com/). If you use MUI components they will
5151
be automatically adapted to look like [DSFR components](https://www.systeme-de-design.gouv.fr/elements-d-interface). See [documentation](https://react-dsfr.etalab.studio/mui-integration).

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "0.30.1",
3+
"version": "0.31.0",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",
@@ -138,6 +138,7 @@
138138
"./SkipLinks": "./dist/SkipLinks.js",
139139
"./Select": "./dist/Select.js",
140140
"./SearchBar": "./dist/SearchBar.js",
141+
"./RadioButtons": "./dist/RadioButtons.js",
141142
"./Quote": "./dist/Quote.js",
142143
"./Pagination": "./dist/Pagination.js",
143144
"./Notice": "./dist/Notice.js",

src/RadioButtons.tsx

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import React, {
2+
useId,
3+
memo,
4+
forwardRef,
5+
type ReactNode,
6+
type CSSProperties,
7+
type InputHTMLAttributes,
8+
type DetailedHTMLProps
9+
} from "react";
10+
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";
15+
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+
};
43+
44+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-radiobutton> */
45+
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+
})
176+
);
177+
178+
RadioButtons.displayName = symToStr({ RadioButtons });
179+
180+
export default RadioButtons;

0 commit comments

Comments
 (0)