@@ -19,6 +19,8 @@ type ComboBoxBaseProps = {
1919 placeholder ?: string ;
2020 disabled ?: boolean ;
2121 className ?: string ;
22+ /** En multi-select, nombre de tags affichés avant de replier le reste en « +N » (défaut 2). */
23+ maxVisibleTags ?: number ;
2224} ;
2325
2426export type ComboBoxProps =
@@ -38,7 +40,7 @@ export type ComboBoxProps =
3840 */
3941const ComboBox = ( rawProps : ComboBoxProps ) => {
4042 const props = { multiple : false , placeholder : 'Sélectionner…' , ...rawProps } satisfies ComboBoxProps ;
41- const { options, value, label, placeholder, disabled, className } = props ;
43+ const { options, value, label, placeholder, disabled, className, maxVisibleTags = 2 } = props ;
4244
4345 const comboboxId = useId ( ) ;
4446 const listboxId = useId ( ) ;
@@ -96,14 +98,26 @@ const ComboBox = (rawProps: ComboBoxProps) => {
9698 }
9799 } ;
98100
101+ const selectAll = ( ) => {
102+ if ( ! props . multiple ) return ;
103+ props . onChange ( options . filter ( ( option ) => ! option . disabled ) . map ( ( option ) => option . key ) ) ;
104+ } ;
105+
99106 const isSelected = ( key : string ) => valueArray . includes ( key ) ;
100107
108+ const allSelected = props . multiple && options . length > 0 && options . every ( ( option ) => option . disabled || isSelected ( option . key ) ) ;
109+
101110 const displayedText = useMemo ( ( ) => {
102111 if ( props . multiple ) return valueArray . map ( ( key ) => options . find ( ( option ) => option . key === key ) ?. label || key ) . join ( ', ' ) ;
103112 if ( ! valueArray [ 0 ] ) return '' ;
104113 return options . find ( ( option ) => option . key === valueArray [ 0 ] ) ?. label || valueArray [ 0 ] ;
105114 } , [ props . multiple , valueArray , options ] ) ;
106115
116+ const hiddenTagsTitle = valueArray
117+ . slice ( maxVisibleTags )
118+ . map ( ( key ) => options . find ( ( option ) => option . key === key ) ?. label || key )
119+ . join ( ', ' ) ;
120+
107121 const activeOptionId = filteredOptions [ highlighted ] ? `${ listboxId } -option-${ filteredOptions [ highlighted ] . key } ` : undefined ;
108122
109123 return (
@@ -157,25 +171,32 @@ const ComboBox = (rawProps: ComboBoxProps) => {
157171 < div className = "flex flex-wrap items-center gap-1 flex-1 min-w-0" >
158172 { props . multiple ? (
159173 valueArray . length > 0 ? (
160- valueArray . map ( ( key ) => {
161- const optionLabel = options . find ( ( option ) => option . key === key ) ?. label || key ;
162- return (
163- < Tag
164- key = { key }
165- dismissible
166- small
167- nativeButtonProps = { {
168- onClick : ( event : React . MouseEvent < HTMLButtonElement > ) => {
169- event . stopPropagation ( ) ;
170- unselectOne ( key ) ;
171- } ,
172- title : optionLabel ,
173- } }
174- >
175- { optionLabel }
174+ < >
175+ { valueArray . slice ( 0 , maxVisibleTags ) . map ( ( key ) => {
176+ const optionLabel = options . find ( ( option ) => option . key === key ) ?. label || key ;
177+ return (
178+ < Tag
179+ key = { key }
180+ dismissible
181+ small
182+ nativeButtonProps = { {
183+ onClick : ( event : React . MouseEvent < HTMLButtonElement > ) => {
184+ event . stopPropagation ( ) ;
185+ unselectOne ( key ) ;
186+ } ,
187+ title : optionLabel ,
188+ } }
189+ >
190+ { optionLabel }
191+ </ Tag >
192+ ) ;
193+ } ) }
194+ { valueArray . length > maxVisibleTags && (
195+ < Tag small title = { hiddenTagsTitle } >
196+ +{ valueArray . length - maxVisibleTags }
176197 </ Tag >
177- ) ;
178- } )
198+ ) }
199+ </ >
179200 ) : (
180201 < span className = "text-gray-500" > { placeholder } </ span >
181202 )
@@ -195,7 +216,20 @@ const ComboBox = (rawProps: ComboBoxProps) => {
195216 className = "border border-solid border-gray-300 shadow-lg"
196217 >
197218 { props . multiple && (
198- < div className = "p-2 border-b border-solid border-gray-200" >
219+ < div className = "flex items-center gap-2 p-2 border-b border-solid border-gray-200" >
220+ < Button
221+ priority = "tertiary"
222+ size = "small"
223+ iconId = "fr-icon-checkbox-circle-line"
224+ onClick = { ( event ) => {
225+ event . preventDefault ( ) ;
226+ event . stopPropagation ( ) ;
227+ selectAll ( ) ;
228+ } }
229+ disabled = { allSelected }
230+ >
231+ Tout sélectionner
232+ </ Button >
199233 < Button
200234 priority = "tertiary"
201235 size = "small"
0 commit comments