1
- import { CSSProperties , forwardRef , memo } from "react" ;
1
+ import {
2
+ CSSProperties ,
3
+ DetailedHTMLProps ,
4
+ forwardRef ,
5
+ InputHTMLAttributes ,
6
+ memo ,
7
+ ReactNode ,
8
+ useId
9
+ } from "react" ;
2
10
import { assert , Equals } from "tsafe" ;
3
- import { fr } from "./fr" ;
11
+ import { fr , FrIconClassName , RiIconClassName } from "./fr" ;
4
12
import React from "react" ;
5
13
import { CxArg } from "tss-react" ;
6
14
import { cx } from "./tools/cx" ;
@@ -9,51 +17,160 @@ import { useAnalyticsId } from "./tools/useAnalyticsId";
9
17
export type SegmentedControlProps = {
10
18
id ?: string ;
11
19
className ?: string ;
20
+ name ?: string ;
12
21
classes ?: Partial <
13
- Record <
14
- | "root"
15
- | "container"
16
- | "row"
17
- | "newsletter-col"
18
- | "newsletter"
19
- | "newsletter-title"
20
- | "newsletter-desc"
21
- | "newsletter-form-wrapper"
22
- | "newsletter-form-hint"
23
- | "social-col"
24
- | "social"
25
- | "social-title"
26
- | "social-buttons"
27
- | "social-buttons-each" ,
28
- CxArg
29
- >
22
+ Record < "root" | "legend" | "elements" | "element-each" | "element-each__label" , CxArg >
30
23
> ;
31
24
style ?: CSSProperties ;
32
- } ;
25
+ small ?: boolean ;
26
+ legend ?: ReactNode ;
27
+ /**
28
+ * Minimum 1, Maximum 5.
29
+ *
30
+ * All with icon or all without icon.
31
+ */
32
+ segments : SegmentedControlProps . Segments ;
33
+ } & ( SegmentedControlProps . WithInlineLegend | SegmentedControlProps . WithHiddenLegend ) ;
33
34
34
35
//https://main--ds-gouv.netlify.app/example/component/segmented/
35
- export namespace SegmentedControlProps { }
36
+ export namespace SegmentedControlProps {
37
+ export type WithInlineLegend = {
38
+ inlineLegend : true ;
39
+ legend : ReactNode ;
40
+ hideLegend ?: never ;
41
+ } ;
42
+
43
+ export type WithHiddenLegend = {
44
+ inlineLegend ?: never ;
45
+ legend ?: ReactNode ;
46
+ hideLegend : true ;
47
+ } ;
48
+
49
+ export type Segment = {
50
+ label : ReactNode ;
51
+ nativeInputProps ?: DetailedHTMLProps <
52
+ InputHTMLAttributes < HTMLInputElement > ,
53
+ HTMLInputElement
54
+ > ;
55
+ iconId ?: FrIconClassName | RiIconClassName ;
56
+ } ;
57
+
58
+ export type SegmentWithIcon = Segment & {
59
+ iconId : FrIconClassName | RiIconClassName ;
60
+ } ;
61
+
62
+ export type SegmentWithoutIcon = Segment & {
63
+ iconId ?: never ;
64
+ } ;
65
+
66
+ export type Segments =
67
+ | [ SegmentWithIcon , SegmentWithIcon ?, SegmentWithIcon ?, SegmentWithIcon ?, SegmentWithIcon ?]
68
+ | [
69
+ SegmentWithoutIcon ,
70
+ SegmentWithoutIcon ?,
71
+ SegmentWithoutIcon ?,
72
+ SegmentWithoutIcon ?,
73
+ SegmentWithoutIcon ?
74
+ ] ;
75
+ }
36
76
37
77
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-segmented-control> */
38
78
export const SegmentedControl = memo (
39
79
forwardRef < HTMLFieldSetElement , SegmentedControlProps > ( ( props , ref ) => {
40
- const { id : props_id , className, classes = { } , style, ...rest } = props ;
80
+ const {
81
+ id : props_id ,
82
+ name : props_name ,
83
+ className,
84
+ classes = { } ,
85
+ style,
86
+ small,
87
+ segments,
88
+ hideLegend,
89
+ inlineLegend,
90
+ legend,
91
+ ...rest
92
+ } = props ;
41
93
42
94
assert < Equals < keyof typeof rest , never > > ( ) ;
43
95
44
96
const id = useAnalyticsId ( {
45
- "defaultIdPrefix" : " fr-follow" ,
97
+ "defaultIdPrefix" : ` fr-segmented ${ props_name === undefined ? "" : `- ${ props_name } ` } ` ,
46
98
"explicitlyProvidedId" : props_id
47
99
} ) ;
48
100
101
+ const getInputId = ( i : number ) => `${ id } -${ i } ` ;
102
+
103
+ const segmentedName = ( function useClosure ( ) {
104
+ const id = useId ( ) ;
105
+
106
+ return props_name ?? `segmented-name-${ id } ` ;
107
+ } ) ( ) ;
108
+
49
109
return (
50
110
< fieldset
51
111
id = { id }
52
- className = { cx ( fr . cx ( "fr-segmented" ) , classes . root , className ) }
112
+ className = { cx (
113
+ fr . cx (
114
+ "fr-segmented" ,
115
+ small && "fr-segmented--sm" ,
116
+ hideLegend && "fr-segmented--no-legend"
117
+ ) ,
118
+ classes . root ,
119
+ className
120
+ ) }
53
121
ref = { ref }
54
122
style = { style }
55
123
{ ...rest }
56
- > </ fieldset >
124
+ >
125
+ { legend !== undefined && (
126
+ < legend
127
+ className = { cx (
128
+ fr . cx (
129
+ "fr-segmented__legend" ,
130
+ inlineLegend && "fr-segmented__legend--inline"
131
+ ) ,
132
+ classes . legend
133
+ ) }
134
+ >
135
+ { legend }
136
+ </ legend >
137
+ ) }
138
+ < div className = { cx ( fr . cx ( "fr-segmented__elements" ) , classes . elements ) } >
139
+ { segments . map ( ( segment , index ) => {
140
+ if ( ! segment ) return null ;
141
+
142
+ const segmentId = getInputId ( index ) ;
143
+ return (
144
+ < div
145
+ className = { cx (
146
+ fr . cx ( "fr-segmented__element" ) ,
147
+ classes [ "element-each" ]
148
+ ) }
149
+ key = { index }
150
+ >
151
+ < input
152
+ { ...segment . nativeInputProps }
153
+ id = { segmentId }
154
+ name = { segmentedName }
155
+ type = "radio"
156
+ />
157
+ < label
158
+ className = { cx (
159
+ fr . cx (
160
+ segment . iconId !== undefined && segment . iconId ,
161
+ "fr-label"
162
+ ) ,
163
+ classes [ "element-each__label" ]
164
+ ) }
165
+ htmlFor = { segmentId }
166
+ >
167
+ { segment . label }
168
+ </ label >
169
+ </ div >
170
+ ) ;
171
+ } ) }
172
+ </ div >
173
+ </ fieldset >
57
174
) ;
58
175
} )
59
176
) ;
0 commit comments