UNPKG

5.1 kBTypeScriptView Raw
1import React, { memo, forwardRef, ReactNode, useState, useEffect, type CSSProperties } from "react";
2import { symToStr } from "tsafe/symToStr";
3import { assert } from "tsafe/assert";
4import type { Equals } from "tsafe";
5import { cx } from "./tools/cx";
6import { fr } from "./fr";
7import { createComponentI18nApi } from "./i18n";
8import { useConstCallback } from "./tools/powerhooks/useConstCallback";
9import { useAnalyticsId } from "./tools/useAnalyticsId";
10
11export type ToggleSwitchProps = ToggleSwitchProps.Controlled | ToggleSwitchProps.Uncontrolled;
12
13export namespace ToggleSwitchProps {
14 export type Common = {
15 id?: string;
16 className?: string;
17 label: ReactNode;
18 helperText?: ReactNode;
19 /** Default: true */
20 showCheckedHint?: boolean;
21 /** Default: false */
22 disabled?: boolean;
23 /** Default: "left" */
24 labelPosition?: "left" | "right";
25 classes?: Partial<Record<"root" | "label" | "input" | "hint", string>>;
26 style?: CSSProperties;
27 name?: string;
28 };
29
30 export type Uncontrolled = Common & {
31 /** Default: "false" */
32 defaultChecked?: boolean;
33 checked?: never;
34 onChange?: (checked: boolean, e: React.ChangeEvent<HTMLInputElement>) => void;
35 inputTitle: string;
36 };
37
38 export type Controlled = Common & {
39 defaultChecked?: never;
40 checked: boolean;
41 onChange: (checked: boolean, e: React.ChangeEvent<HTMLInputElement>) => void;
42 inputTitle?: string;
43 };
44}
45
46/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-toggleswitch> */
47export const ToggleSwitch = memo(
48 forwardRef<HTMLDivElement, ToggleSwitchProps>((props, ref) => {
49 const {
50 id: id_props,
51 className,
52 label,
53 helperText,
54 defaultChecked = false,
55 checked: props_checked,
56 showCheckedHint = true,
57 disabled = false,
58 labelPosition = "right",
59 classes = {},
60 onChange,
61 inputTitle,
62 style,
63 name,
64 ...rest
65 } = props;
66
67 const id = useAnalyticsId({
68 "defaultIdPrefix": "fr-toggle",
69 "explicitlyProvidedId": id_props
70 });
71
72 const inputId = `${id}-input`;
73
74 const hintId = `${id}-hint-text`;
75
76 const [checked, setChecked] = useState(defaultChecked);
77
78 useEffect(() => {
79 if (defaultChecked === undefined) {
80 return;
81 }
82
83 setChecked(defaultChecked);
84 }, [defaultChecked]);
85
86 assert<Equals<keyof typeof rest, never>>();
87
88 const { t } = useTranslation();
89
90 const onInputChange = useConstCallback((e: React.ChangeEvent<HTMLInputElement>) => {
91 const checked = e.currentTarget.checked;
92
93 if (props_checked === undefined) {
94 setChecked(checked);
95 onChange?.(checked, e);
96 } else {
97 onChange(checked, e);
98 }
99 });
100
101 return (
102 <div
103 id={id}
104 className={cx(
105 fr.cx("fr-toggle", labelPosition === "left" && "fr-toggle--label-left"),
106 classes.root,
107 className
108 )}
109 ref={ref}
110 style={style}
111 >
112 <input
113 onChange={onInputChange}
114 type="checkbox"
115 disabled={disabled || undefined}
116 className={cx(fr.cx("fr-toggle__input"), classes.input)}
117 aria-describedby={hintId}
118 id={inputId}
119 title={inputTitle}
120 checked={props_checked ?? checked}
121 name={name}
122 />
123 <label
124 className={cx(fr.cx("fr-toggle__label"), classes.label)}
125 htmlFor={inputId}
126 {...(showCheckedHint && {
127 "data-fr-checked-label": t("checked"),
128 "data-fr-unchecked-label": t("unchecked")
129 })}
130 >
131 {label}
132 </label>
133 {helperText && (
134 <p className={cx(fr.cx("fr-hint-text"), classes.hint)} id={hintId}>
135 {helperText}
136 </p>
137 )}
138 </div>
139 );
140 })
141);
142
143ToggleSwitch.displayName = symToStr({ ToggleSwitch });
144
145const { useTranslation, addToggleSwitchTranslations } = createComponentI18nApi({
146 "componentName": symToStr({ ToggleSwitch }),
147 "frMessages": {
148 /* spell-checker: disable */
149 "checked": "Activé",
150 "unchecked": "Désactivé"
151 /* spell-checker: enable */
152 }
153});
154
155addToggleSwitchTranslations({
156 "lang": "en",
157 "messages": {
158 "checked": "Active",
159 "unchecked": "Inactive"
160 }
161});
162
163export { addToggleSwitchTranslations };
164
165export default ToggleSwitch;
166
\No newline at end of file