1- import React , { memo , forwardRef , ReactNode , useId , useState } from "react" ;
1+ import React , { memo , forwardRef , ReactNode , useId , useState , useEffect } from "react" ;
22import { symToStr } from "tsafe/symToStr" ;
33import { assert } from "tsafe/assert" ;
44import type { Equals } from "tsafe" ;
5-
65import { cx } from "./tools/cx" ;
76import { fr } from "./fr" ;
87import { createComponentI18nApi } from "./i18n" ;
@@ -14,10 +13,10 @@ export namespace ToggleSwitchProps {
1413 export type Common = {
1514 className ?: string ;
1615 label : ReactNode ;
17- text ?: ReactNode ;
18- /** Default: " true" */
16+ helperText ?: ReactNode ;
17+ /** Default: true */
1918 showCheckedHint ?: boolean ;
20- /** Default: " false" */
19+ /** Default: false */
2120 disabled ?: boolean ;
2221 /** Default: "left" */
2322 labelPosition ?: "left" | "right" ;
@@ -27,90 +26,70 @@ export namespace ToggleSwitchProps {
2726 export type Uncontrolled = Common & {
2827 /** Default: "false" */
2928 defaultChecked ?: boolean ;
30- checked ?: undefined ;
31- onChange ?: ( event : React . ChangeEvent < HTMLInputElement > ) => void ;
29+ checked ?: never ;
30+ onChange ?: ( checked : boolean , e : React . ChangeEvent < HTMLInputElement > ) => void ;
31+ inputTitle : string ;
3232 } ;
3333
3434 export type Controlled = Common & {
35- /** Default: "false" */
36- defaultChecked ?: undefined ;
35+ defaultChecked ?: never ;
3736 checked : boolean ;
38- onChange : ( event : React . ChangeEvent < HTMLInputElement > ) => void ;
37+ onChange : ( checked : boolean , e : React . ChangeEvent < HTMLInputElement > ) => void ;
38+ inputTitle ?: string ;
3939 } ;
4040}
4141
42- export type ToggleSwitchGroupProps = {
43- className ?: string ;
44- /** Needs at least one ToggleSwitch */
45- togglesProps : [ ToggleSwitchProps , ...ToggleSwitchProps [ ] ] ;
46- /** Default: "true" */
47- showCheckedHint ?: boolean ;
48- /** Default: "left" */
49- labelPosition ?: "left" | "right" ;
50- classes ?: Partial < Record < "root" | "li" , string > > ;
51- } ;
52-
53- /** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-toggleswitch> */
54- export const ToggleSwitchGroup = memo < ToggleSwitchGroupProps > ( props => {
55- const {
56- className,
57- togglesProps,
58- showCheckedHint = true ,
59- labelPosition = "right" ,
60- classes = { } ,
61- ...rest
62- } = props ;
63-
64- assert < Equals < keyof typeof rest , never > > ( ) ;
65-
66- return (
67- < ul className = { cx ( fr . cx ( "fr-toggle__list" ) , classes . root , className ) } { ...rest } >
68- { togglesProps &&
69- togglesProps . map ( ( toggleProps , i ) => (
70- < li key = { i } className = { classes . li } >
71- < ToggleSwitch
72- { ...{
73- ...toggleProps ,
74- showCheckedHint,
75- labelPosition
76- } }
77- />
78- </ li >
79- ) ) }
80- </ ul >
81- ) ;
82- } ) ;
83-
8442/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-toggleswitch> */
8543export const ToggleSwitch = memo (
8644 forwardRef < HTMLDivElement , ToggleSwitchProps > ( ( props , ref ) => {
8745 const {
8846 className,
8947 label,
90- text ,
48+ helperText ,
9149 defaultChecked = false ,
92- checked,
50+ checked : props_checked ,
9351 showCheckedHint = true ,
9452 disabled = false ,
9553 labelPosition = "right" ,
9654 classes = { } ,
9755 onChange,
56+ inputTitle,
9857 ...rest
9958 } = props ;
10059
101- const [ checkedState , setCheckState ] = useState ( defaultChecked ) ;
60+ const [ checked , setChecked ] = useState ( defaultChecked ) ;
61+
62+ useEffect ( ( ) => {
63+ if ( defaultChecked === undefined ) {
64+ return ;
65+ }
10266
103- const checkedValue = checked !== undefined ? checked : checkedState ;
67+ setChecked ( defaultChecked ) ;
68+ } , [ defaultChecked ] ) ;
10469
10570 assert < Equals < keyof typeof rest , never > > ( ) ;
10671
107- const inputId = useId ( ) ;
72+ const { inputId, hintId } = ( function useClosure ( ) {
73+ const id = useId ( ) ;
74+
75+ const inputId = `toggle-${ id } ` ;
76+
77+ const hintId = `toggle-${ id } -hint-text` ;
78+
79+ return { inputId, hintId } ;
80+ } ) ( ) ;
10881
10982 const { t } = useTranslation ( ) ;
11083
111- const onInputChange = useConstCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
112- setCheckState ( event . currentTarget . checked ) ;
113- onChange ?.( event ) ;
84+ const onInputChange = useConstCallback ( ( e : React . ChangeEvent < HTMLInputElement > ) => {
85+ const checked = e . currentTarget . checked ;
86+
87+ if ( props_checked === undefined ) {
88+ setChecked ( checked ) ;
89+ onChange ?.( checked , e ) ;
90+ } else {
91+ onChange ( checked , e ) ;
92+ }
11493 } ) ;
11594
11695 return (
@@ -127,9 +106,10 @@ export const ToggleSwitch = memo(
127106 type = "checkbox"
128107 disabled = { disabled || undefined }
129108 className = { cx ( fr . cx ( "fr-toggle__input" ) , classes . input ) }
130- aria-describedby = { ` ${ inputId } -hint-text` }
109+ aria-describedby = { hintId }
131110 id = { inputId }
132- checked = { checkedValue }
111+ title = { inputTitle }
112+ checked = { props_checked ?? checked }
133113 />
134114 < label
135115 className = { cx ( fr . cx ( "fr-toggle__label" ) , classes . label ) }
@@ -141,12 +121,9 @@ export const ToggleSwitch = memo(
141121 >
142122 { label }
143123 </ label >
144- { text && (
145- < p
146- className = { cx ( fr . cx ( "fr-hint-text" ) , classes . hint ) }
147- id = { `${ inputId } -hint-text` }
148- >
149- { text }
124+ { helperText && (
125+ < p className = { cx ( fr . cx ( "fr-hint-text" ) , classes . hint ) } id = { hintId } >
126+ { helperText }
150127 </ p >
151128 ) }
152129 </ div >
0 commit comments