@@ -149,6 +149,14 @@ export interface ResultsListProps {
149149 format : 'csv' | 'json' | 'xlsx' | 'xml' | 'yaml' ,
150150 filename : string
151151 ) => void ;
152+ /** True once the user has clicked "Select all N entries matching this search" */
153+ selectAllMatching ?: boolean ;
154+ onSelectAllMatchingChange ?: ( value : boolean ) => void ;
155+ /** Exports every entry matching the current search filters, not just the fetched page(s) */
156+ onExportAllMatching ?: (
157+ format : 'csv' | 'json' | 'xlsx' | 'xml' | 'yaml' ,
158+ filename : string
159+ ) => void ;
152160 contentTypeMap ?: ContentTypeMap ;
153161 userMap ?: UserMap ;
154162 spaceId ?: string ;
@@ -338,6 +346,9 @@ export function ResultsList({
338346 selectedIds = [ ] ,
339347 onSelectionChange,
340348 onExportSelected,
349+ selectAllMatching = false ,
350+ onSelectAllMatchingChange,
351+ onExportAllMatching,
341352 contentTypeMap = { } ,
342353 userMap = { } ,
343354 spaceId = '' ,
@@ -503,6 +514,12 @@ export function ResultsList({
503514
504515 const handleSelectAll = ( ) => {
505516 if ( ! onSelectionChange ) return ;
517+ if ( selectAllMatching ) {
518+ // Unchecking while every matching entry is selected drops back to no selection.
519+ onSelectAllMatchingChange ?.( false ) ;
520+ onSelectionChange ( [ ] ) ;
521+ return ;
522+ }
506523 if ( allSelected ) {
507524 onSelectionChange ( selectedIds . filter ( ( id ) => ! allCurrentIds . includes ( id ) ) ) ;
508525 } else {
@@ -513,13 +530,31 @@ export function ResultsList({
513530
514531 const handleSelectOne = ( id : string ) => {
515532 if ( ! onSelectionChange ) return ;
533+ if ( selectAllMatching ) {
534+ // Deselecting a single row while every matching entry is selected falls back
535+ // to page-level selection (minus that row) rather than tracking exclusions.
536+ onSelectAllMatchingChange ?.( false ) ;
537+ onSelectionChange ( allCurrentIds . filter ( ( currentId ) => currentId !== id ) ) ;
538+ return ;
539+ }
516540 if ( selectedIds . includes ( id ) ) {
517541 onSelectionChange ( selectedIds . filter ( ( selectedId ) => selectedId !== id ) ) ;
518542 } else {
519543 onSelectionChange ( [ ...selectedIds , id ] ) ;
520544 }
521545 } ;
522546
547+ const handleClearSelection = ( ) => {
548+ onSelectAllMatchingChange ?.( false ) ;
549+ onSelectionChange ?.( [ ] ) ;
550+ } ;
551+
552+ const showSelectAllBanner =
553+ ! selectAllMatching &&
554+ allSelected &&
555+ totalCount !== undefined &&
556+ totalCount > allCurrentIds . length ;
557+
523558 const getTitle = ( entry : SearchResult ) : string => {
524559 if ( ! entry . fields ) return entry . sys . id ;
525560 const contentTypeId = entry . sys . contentType . sys . id ;
@@ -606,18 +641,38 @@ export function ResultsList({
606641 </ Flex >
607642
608643 { /* Selection bar */ }
609- { selectedIds . length > 0 && (
644+ { ( selectedIds . length > 0 || selectAllMatching ) && (
610645 < div style = { { padding : '0 24px' } } >
611646 < Flex
612647 alignItems = "center"
613648 gap = "spacingS"
649+ flexWrap = "wrap"
614650 style = { {
615651 padding : '12px 0' ,
616652 borderTop : `1px solid ${ tokens . gray200 } ` ,
617653 borderBottom : `1px solid ${ tokens . gray200 } ` ,
618654 } } >
619655 < Text fontSize = "fontSizeS" fontColor = "gray700" >
620- { selectedIds . length } { selectedIds . length === 1 ? 'entry' : 'entries' } selected:
656+ { selectAllMatching ? (
657+ < >
658+ All { ( totalCount ?? selectedIds . length ) . toLocaleString ( ) } { ' ' }
659+ { ( totalCount ?? selectedIds . length ) === 1 ? 'entry' : 'entries' } matching this
660+ search are selected.{ ' ' }
661+ < TextLink as = "button" onClick = { handleClearSelection } >
662+ Clear selection
663+ </ TextLink >
664+ </ >
665+ ) : showSelectAllBanner ? (
666+ < >
667+ All { allCurrentIds . length } { allCurrentIds . length === 1 ? 'entry' : 'entries' } on
668+ this page are selected.{ ' ' }
669+ < TextLink as = "button" onClick = { ( ) => onSelectAllMatchingChange ?.( true ) } >
670+ Select all { totalCount ?. toLocaleString ( ) } entries matching this search
671+ </ TextLink >
672+ </ >
673+ ) : (
674+ `${ selectedIds . length } ${ selectedIds . length === 1 ? 'entry' : 'entries' } selected:`
675+ ) }
621676 </ Text >
622677 { onExportSelected && (
623678 < Button
@@ -635,7 +690,8 @@ export function ResultsList({
635690 { /* Table — horizontal scroll when field columns overflow.
636691 tableLayout: fixed lets us set exact column widths so sticky
637692 left offsets are reliable pixel values. */ }
638- < div style = { { padding : `${ selectedIds . length > 0 ? '24px' : '0' } 24px 24px` } } >
693+ < div
694+ style = { { padding : `${ selectedIds . length > 0 || selectAllMatching ? '24px' : '0' } 24px 24px` } } >
639695 < div style = { { border : '1px solid #E7EBEE' , borderRadius : '6px' , overflow : 'hidden' } } >
640696 < div style = { { overflowX : 'auto' , width : '100%' } } >
641697 < Table
@@ -672,8 +728,8 @@ export function ResultsList({
672728 } } >
673729 { onSelectionChange && (
674730 < Checkbox
675- isChecked = { allSelected }
676- isIndeterminate = { someSelected }
731+ isChecked = { allSelected || selectAllMatching }
732+ isIndeterminate = { someSelected && ! selectAllMatching }
677733 onChange = { handleSelectAll }
678734 aria-label = "Select all entries on this page"
679735 />
@@ -900,7 +956,10 @@ export function ResultsList({
900956 < div style = { { padding : '16px 24px 0' } } >
901957 { isExporting ? (
902958 < Text >
903- { exportProgress ?. message || `Exporting ${ selectedIds . length } selected entries` }
959+ { exportProgress ?. message ||
960+ ( selectAllMatching
961+ ? `Exporting ${ ( totalCount ?? 0 ) . toLocaleString ( ) } matching entries`
962+ : `Exporting ${ selectedIds . length } selected entries` ) }
904963 </ Text >
905964 ) : (
906965 < >
@@ -944,6 +1003,11 @@ export function ResultsList({
9441003 isDisabled = { isExporting }
9451004 onClick = { ( ) => {
9461005 const today = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ;
1006+ if ( selectAllMatching ) {
1007+ const resolvedFilename = exportFilename . trim ( ) || `all-matching-${ today } ` ;
1008+ onExportAllMatching ?.( exportFormat , resolvedFilename ) ;
1009+ return ;
1010+ }
9471011 const resolvedFilename =
9481012 exportFilename . trim ( ) || `selected-${ selectedIds . length } -entries-${ today } ` ;
9491013 onExportSelected ?.( selectedIds , exportFormat , resolvedFilename ) ;
0 commit comments