1 | import React, { memo, forwardRef, ReactNode, useState, useEffect, type CSSProperties } from "react";
|
2 | import { symToStr } from "tsafe/symToStr";
|
3 | import { assert } from "tsafe/assert";
|
4 | import type { Equals } from "tsafe";
|
5 | import { cx } from "./tools/cx";
|
6 | import { fr } from "./fr";
|
7 | import { createComponentI18nApi } from "./i18n";
|
8 | import { useConstCallback } from "./tools/powerhooks/useConstCallback";
|
9 | import { useAnalyticsId } from "./tools/useAnalyticsId";
|
10 |
|
11 | export type ToggleSwitchProps = ToggleSwitchProps.Controlled | ToggleSwitchProps.Uncontrolled;
|
12 |
|
13 | export namespace ToggleSwitchProps {
|
14 | export type Common = {
|
15 | id?: string;
|
16 | className?: string;
|
17 | label: ReactNode;
|
18 | helperText?: ReactNode;
|
19 |
|
20 | showCheckedHint?: boolean;
|
21 |
|
22 | disabled?: boolean;
|
23 |
|
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 |
|
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 |
|
47 | export 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 |
|
143 | ToggleSwitch.displayName = symToStr({ ToggleSwitch });
|
144 |
|
145 | const { useTranslation, addToggleSwitchTranslations } = createComponentI18nApi({
|
146 | "componentName": symToStr({ ToggleSwitch }),
|
147 | "frMessages": {
|
148 |
|
149 | "checked": "Activé",
|
150 | "unchecked": "Désactivé"
|
151 |
|
152 | }
|
153 | });
|
154 |
|
155 | addToggleSwitchTranslations({
|
156 | "lang": "en",
|
157 | "messages": {
|
158 | "checked": "Active",
|
159 | "unchecked": "Inactive"
|
160 | }
|
161 | });
|
162 |
|
163 | export { addToggleSwitchTranslations };
|
164 |
|
165 | export default ToggleSwitch;
|
166 |
|
\ | No newline at end of file |