Skip to content

Commit 06db64e

Browse files
authored
feat(data-modeling): add collection search and fix styles COMPASS-9308 COMPASS-9309 (#6895)
* fix styles and add collection search * handle collections search * always render footer * fix selected collections on search * pr feedback
1 parent 89d3463 commit 06db64e

File tree

1 file changed

+120
-39
lines changed

1 file changed

+120
-39
lines changed

packages/compass-data-modeling/src/components/new-diagram-form.tsx

Lines changed: 120 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import { connect } from 'react-redux';
33
import type { DataModelingState } from '../store/reducer';
44
import { 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

3740
const 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

4155
const 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

81101
const 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+
85172
type 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

Comments
 (0)