1- import React , { useMemo } from 'react' ;
1+ import React , { useMemo , useState } from 'react' ;
22import { connect } from 'react-redux' ;
33import type { DataModelingState } from '../store/reducer' ;
44import { useConnectionsList } from '@mongodb-js/compass-connections/provider' ;
@@ -31,11 +31,25 @@ import {
3131 Select ,
3232 SelectTable ,
3333 spacing ,
34+ SpinLoader ,
35+ Body ,
3436 TextInput ,
37+ SearchInput ,
3538} from '@mongodb-js/compass-components' ;
3639
3740const footerStyles = css ( {
38- gap : spacing [ 200 ] ,
41+ flexDirection : 'row' ,
42+ alignItems : 'center' ,
43+ } ) ;
44+
45+ const footerTextStyles = css ( { marginRight : 'auto' } ) ;
46+
47+ const footerActionsStyles = css ( { display : 'flex' , gap : spacing [ 200 ] } ) ;
48+
49+ const formContainerStyles = css ( {
50+ display : 'flex' ,
51+ flexDirection : 'column' ,
52+ gap : spacing [ 400 ] ,
3953} ) ;
4054
4155const FormStepContainer : React . FunctionComponent < {
@@ -47,6 +61,7 @@ const FormStepContainer: React.FunctionComponent<{
4761 isNextDisabled : boolean ;
4862 nextLabel : string ;
4963 previousLabel : string ;
64+ footerText ?: React . ReactNode ;
5065} > = ( {
5166 title,
5267 description,
@@ -57,31 +72,103 @@ const FormStepContainer: React.FunctionComponent<{
5772 nextLabel,
5873 previousLabel,
5974 children,
75+ footerText,
6076} ) => {
6177 return (
6278 < >
6379 < ModalHeader title = { title } subtitle = { description } > </ ModalHeader >
6480 < ModalBody > { children } </ ModalBody >
6581 < ModalFooter className = { footerStyles } >
66- < Button
67- onClick = { onNextClick }
68- disabled = { isNextDisabled }
69- isLoading = { isLoading }
70- data-testid = "new-diagram-confirm-button"
71- variant = "primary"
72- >
73- { nextLabel }
74- </ Button >
75- < Button onClick = { onPreviousClick } > { previousLabel } </ Button >
82+ < Body className = { footerTextStyles } > { footerText } </ Body >
83+ < div className = { footerActionsStyles } >
84+ < Button onClick = { onPreviousClick } > { previousLabel } </ Button >
85+ < Button
86+ onClick = { onNextClick }
87+ disabled = { isNextDisabled }
88+ isLoading = { isLoading }
89+ data-testid = "new-diagram-confirm-button"
90+ variant = "primary"
91+ loadingIndicator = { < SpinLoader /> }
92+ >
93+ { nextLabel }
94+ </ Button >
95+ </ div >
7696 </ ModalFooter >
7797 </ >
7898 ) ;
7999} ;
80100
81101const selectTableStyles = css ( {
82- maxHeight : 300 ,
102+ height : 300 ,
103+ overflow : 'scroll' ,
83104} ) ;
84105
106+ function SelectCollectionsStep ( {
107+ collections,
108+ selectedCollections,
109+ onCollectionsSelect,
110+ } : {
111+ collections : string [ ] ;
112+ selectedCollections : string [ ] ;
113+ onCollectionsSelect : ( colls : string [ ] ) => void ;
114+ } ) {
115+ const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
116+ const filteredCollections = useMemo ( ( ) => {
117+ try {
118+ const regex = new RegExp ( searchTerm , 'i' ) ;
119+ return collections . filter ( ( x ) => regex . test ( x ) ) ;
120+ } catch {
121+ return collections ;
122+ }
123+ } , [ collections , searchTerm ] ) ;
124+ return (
125+ < FormFieldContainer className = { formContainerStyles } >
126+ < SearchInput
127+ aria-label = "Search collections"
128+ value = { searchTerm }
129+ data-testid = "new-diagram-search-collections"
130+ onChange = { ( e ) => {
131+ setSearchTerm ( e . target . value ) ;
132+ } }
133+ />
134+ < SelectTable
135+ className = { selectTableStyles }
136+ items = { filteredCollections . map ( ( collName ) => {
137+ return {
138+ id : collName ,
139+ selected : selectedCollections . includes ( collName ) ,
140+ 'data-testid' : `new-diagram-collection-checkbox-${ collName } ` ,
141+ } ;
142+ } ) }
143+ columns = { [ [ 'id' , 'Collection Name' ] ] }
144+ onChange = { ( items ) => {
145+ // When a user is searching, less collections are shown to the user
146+ // and we need to keep existing selected collections selected.
147+ const currentSelectedItems = selectedCollections . filter (
148+ ( collName ) => {
149+ const item = items . find ( ( x ) => x . id === collName ) ;
150+ // The already selected item was not shown to the user (using search),
151+ // and we have to keep it selected.
152+ return item ? item . selected : true ;
153+ }
154+ ) ;
155+
156+ const newSelectedItems = items
157+ . filter ( ( item ) => {
158+ return item . selected ;
159+ } )
160+ . map ( ( item ) => {
161+ return item . id ;
162+ } ) ;
163+ onCollectionsSelect (
164+ Array . from ( new Set ( [ ...newSelectedItems , ...currentSelectedItems ] ) )
165+ ) ;
166+ } }
167+ > </ SelectTable >
168+ </ FormFieldContainer >
169+ ) ;
170+ }
171+
85172type NewDiagramFormProps = {
86173 isModalOpen : boolean ;
87174 formStep :
@@ -145,6 +232,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
145232 isConfirmDisabled,
146233 onCancelAction,
147234 cancelLabel,
235+ footerText,
148236 } = useMemo ( ( ) => {
149237 switch ( currentStep ) {
150238 case 'enter-name' :
@@ -177,14 +265,23 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
177265 case 'select-collections' :
178266 return {
179267 title : `Select collections for ${ selectedDatabase ?? '' } ` ,
180- description :
181- 'These collections will be included to the generated diagram' ,
268+ description : `${
269+ collections . length === 1 ? 'This collection' : 'These collections'
270+ } will be included in your generated diagram.`,
182271 onConfirmAction : onCollectionsSelectionConfirm ,
183272 confirmActionLabel : 'Generate' ,
184273 isConfirmDisabled :
185274 ! selectedCollections || selectedCollections . length === 0 ,
186275 onCancelAction : onDatabaseSelectCancel ,
187276 cancelLabel : 'Back' ,
277+ footerText : (
278+ < >
279+ < strong > { selectedCollections . length } </ strong > /
280+ < strong > { collections . length } </ strong > total{ ' ' }
281+ { collections . length === 1 ? 'collection' : 'collections' } { ' ' }
282+ selected.
283+ </ >
284+ ) ,
188285 } ;
189286 }
190287 } , [
@@ -201,6 +298,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
201298 selectedCollections ,
202299 selectedConnectionId ,
203300 selectedDatabase ,
301+ collections ,
204302 ] ) ;
205303
206304 const formContent = useMemo ( ( ) => {
@@ -220,7 +318,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
220318 ) ;
221319 case 'select-connection' :
222320 return (
223- < FormFieldContainer >
321+ < FormFieldContainer className = { formContainerStyles } >
224322 < Select
225323 label = ""
226324 aria-label = "Select connection"
@@ -266,29 +364,11 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
266364 ) ;
267365 case 'select-collections' :
268366 return (
269- < FormFieldContainer >
270- < SelectTable
271- className = { selectTableStyles }
272- items = { collections . map ( ( collName ) => {
273- return {
274- id : collName ,
275- selected : selectedCollections . includes ( collName ) ,
276- 'data-testid' : `new-diagram-collection-checkbox-${ collName } ` ,
277- } ;
278- } ) }
279- columns = { [ [ 'id' , 'Collection Name' ] ] }
280- onChange = { ( items ) => {
281- const selectedItems = items
282- . filter ( ( item ) => {
283- return item . selected ;
284- } )
285- . map ( ( item ) => {
286- return item . id ;
287- } ) ;
288- onCollectionsSelect ( selectedItems ) ;
289- } }
290- > </ SelectTable >
291- </ FormFieldContainer >
367+ < SelectCollectionsStep
368+ collections = { collections }
369+ onCollectionsSelect = { onCollectionsSelect }
370+ selectedCollections = { selectedCollections }
371+ />
292372 ) ;
293373 }
294374 } , [
@@ -325,6 +405,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
325405 previousLabel = { cancelLabel }
326406 isNextDisabled = { isConfirmDisabled }
327407 isLoading = { isLoading }
408+ footerText = { footerText }
328409 >
329410 { formContent }
330411 { error && < ErrorSummary errors = { [ error . message ] } /> }
0 commit comments