Skip to content

Commit 3b3ad4b

Browse files
authored
feat(indexes): fixed table header COMPASS-6042 (#3407)
1 parent e8111ea commit 3b3ad4b

File tree

5 files changed

+111
-87
lines changed

5 files changed

+111
-87
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-indexes/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@
7777
"eslint": "^7.25.0",
7878
"hadron-app": "^5.0.0",
7979
"hadron-app-registry": "^9.0.0",
80-
"lodash.contains": "^2.4.3",
8180
"lodash.clonedeep": "^4.5.0",
81+
"lodash.contains": "^2.4.3",
8282
"mocha": "^8.4.0",
8383
"mongodb": "^4.6.0",
8484
"mongodb-data-service": "^22.0.0",
@@ -88,6 +88,7 @@
8888
"prop-types": "^15.7.2",
8989
"react-dom": "^16.14.0",
9090
"react-redux": "^5.0.6",
91+
"react-virtualized-auto-sizer": "^1.0.6",
9192
"redux": "^4.1.2",
9293
"redux-thunk": "^2.3.0",
9394
"rimraf": "^3.0.2",

packages/compass-indexes/src/components/indexes-table/indexes-table.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ const renderIndexList = (
113113
canDeleteIndex={true}
114114
onSortTable={() => {}}
115115
onDeleteIndex={() => {}}
116+
scrollHeight={400}
116117
{...props}
117118
/>
118119
);

packages/compass-indexes/src/components/indexes-table/indexes-table.tsx

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useMemo, useRef, useEffect } from 'react';
22
import {
33
css,
44
Table,
@@ -7,6 +7,7 @@ import {
77
Cell,
88
cx,
99
spacing,
10+
uiColors,
1011
} from '@mongodb-js/compass-components';
1112

1213
import NameField from './name-field';
@@ -60,20 +61,31 @@ const nameFieldStyles = css({
6061
paddingBottom: spacing[2],
6162
});
6263

64+
const tableStyles = css({
65+
thead: {
66+
position: 'sticky',
67+
top: 0,
68+
background: uiColors.white,
69+
zIndex: 1,
70+
},
71+
});
72+
6373
type IndexesTableProps = {
64-
darkMode?: boolean;
6574
indexes: IndexDefinition[];
6675
canDeleteIndex: boolean;
76+
scrollHeight: number;
6777
onSortTable: (column: SortColumn, direction: SortDirection) => void;
6878
onDeleteIndex: (index: IndexDefinition) => void;
6979
};
7080

7181
export const IndexesTable: React.FunctionComponent<IndexesTableProps> = ({
7282
indexes,
7383
canDeleteIndex,
84+
scrollHeight,
7485
onSortTable,
7586
onDeleteIndex,
7687
}) => {
88+
const containerRef = useRef<HTMLDivElement>(null);
7789
const columns = useMemo(() => {
7890
const sortColumns: SortColumn[] = [
7991
'Name and Definition',
@@ -102,57 +114,73 @@ export const IndexesTable: React.FunctionComponent<IndexesTableProps> = ({
102114
return _columns;
103115
}, [canDeleteIndex, onSortTable]);
104116

117+
useEffect(() => {
118+
/**
119+
* For table header to be sticky, the wrapper element of table needs to have a height.
120+
* LG wraps table in a div at multiple levels, so height can not be direclty applied to the wrapper we use.
121+
*/
122+
const container =
123+
containerRef.current?.getElementsByTagName('table')[0]?.parentElement;
124+
if (container) {
125+
container.style.height = `${scrollHeight}px`;
126+
}
127+
}, [scrollHeight]);
128+
105129
return (
106-
<Table
107-
data={indexes}
108-
columns={columns}
109-
data-testid="indexes-list"
110-
aria-label="Indexes List Table"
111-
>
112-
{({ datum: index }) => (
113-
<Row
114-
key={index.name}
115-
data-testid={`index-row-${index.name}`}
116-
className={rowStyles}
117-
>
118-
<Cell data-testid="index-name-field" className={cellStyles}>
119-
<div className={nameFieldStyles}>
120-
<NameField name={index.name} keys={index.fields.serialize()} />
121-
</div>
122-
</Cell>
123-
<Cell data-testid="index-type-field" className={cellStyles}>
124-
<TypeField type={index.type} extra={index.extra} />
125-
</Cell>
126-
<Cell data-testid="index-size-field" className={cellStyles}>
127-
<SizeField size={index.size} relativeSize={index.relativeSize} />
128-
</Cell>
129-
<Cell data-testid="index-usage-field" className={cellStyles}>
130-
<UsageField usage={index.usageCount} since={index.usageSince} />
131-
</Cell>
132-
<Cell data-testid="index-property-field" className={cellStyles}>
133-
<PropertyField
134-
cardinality={index.cardinality}
135-
extra={index.extra}
136-
properties={index.properties}
137-
/>
138-
</Cell>
139-
{/* Delete column is conditional */}
140-
{canDeleteIndex && (
141-
<Cell data-testid="index-actions-field" className={cellStyles}>
142-
{index.name !== '_id_' && index.extra.status !== 'inprogress' && (
143-
<div
144-
className={cx(indexActionsCellStyles, 'index-actions-cell')}
145-
>
146-
<IndexActions
147-
index={index}
148-
onDeleteIndex={onDeleteIndex}
149-
></IndexActions>
150-
</div>
151-
)}
130+
// LG table does not forward ref
131+
<div ref={containerRef}>
132+
<Table
133+
className={tableStyles}
134+
data={indexes}
135+
columns={columns}
136+
data-testid="indexes-list"
137+
aria-label="Indexes List Table"
138+
>
139+
{({ datum: index }) => (
140+
<Row
141+
key={index.name}
142+
data-testid={`index-row-${index.name}`}
143+
className={rowStyles}
144+
>
145+
<Cell data-testid="index-name-field" className={cellStyles}>
146+
<div className={nameFieldStyles}>
147+
<NameField name={index.name} keys={index.fields.serialize()} />
148+
</div>
149+
</Cell>
150+
<Cell data-testid="index-type-field" className={cellStyles}>
151+
<TypeField type={index.type} extra={index.extra} />
152+
</Cell>
153+
<Cell data-testid="index-size-field" className={cellStyles}>
154+
<SizeField size={index.size} relativeSize={index.relativeSize} />
155+
</Cell>
156+
<Cell data-testid="index-usage-field" className={cellStyles}>
157+
<UsageField usage={index.usageCount} since={index.usageSince} />
158+
</Cell>
159+
<Cell data-testid="index-property-field" className={cellStyles}>
160+
<PropertyField
161+
cardinality={index.cardinality}
162+
extra={index.extra}
163+
properties={index.properties}
164+
/>
152165
</Cell>
153-
)}
154-
</Row>
155-
)}
156-
</Table>
166+
{/* Delete column is conditional */}
167+
{canDeleteIndex && (
168+
<Cell data-testid="index-actions-field" className={cellStyles}>
169+
{index.name !== '_id_' && index.extra.status !== 'inprogress' && (
170+
<div
171+
className={cx(indexActionsCellStyles, 'index-actions-cell')}
172+
>
173+
<IndexActions
174+
index={index}
175+
onDeleteIndex={onDeleteIndex}
176+
></IndexActions>
177+
</div>
178+
)}
179+
</Cell>
180+
)}
181+
</Row>
182+
)}
183+
</Table>
184+
</div>
157185
);
158186
};

packages/compass-indexes/src/components/indexes/indexes.tsx

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { css, Card, spacing } from '@mongodb-js/compass-components';
33
import { connect } from 'react-redux';
44
import type AppRegistry from 'hadron-app-registry';
5+
import AutoSizer from 'react-virtualized-auto-sizer';
56

67
import { sortIndexes, dropFailedIndex } from '../../modules/indexes';
78
import type {
@@ -18,21 +19,10 @@ import type { RootState } from '../../modules';
1819
const containerStyles = css({
1920
margin: spacing[3],
2021
padding: spacing[3],
21-
display: 'grid',
22-
gridTemplateAreas: `
23-
'toolbar'
24-
'indexTable'
25-
`,
26-
width: '100%',
2722
overflow: 'hidden',
28-
alignContent: 'start',
29-
});
30-
const toolbarStyles = css({
31-
gridArea: 'toolbar',
32-
});
33-
const indexTableStyles = css({
34-
gridArea: 'indexTable',
35-
overflow: 'auto',
23+
display: 'flex',
24+
flexDirection: 'column',
25+
flex: 1,
3626
});
3727

3828
type IndexesProps = {
@@ -71,27 +61,29 @@ export const Indexes: React.FunctionComponent<IndexesProps> = ({
7161
};
7262
return (
7363
<Card className={containerStyles} data-testid="indexes">
74-
<div className={toolbarStyles}>
75-
<IndexesToolbar
76-
isWritable={isWritable}
77-
isReadonly={isReadonly}
78-
isReadonlyView={isReadonlyView}
79-
errorMessage={error}
80-
localAppRegistry={localAppRegistry}
81-
isRefreshing={isRefreshing}
82-
writeStateDescription={description}
83-
onRefreshIndexes={refreshIndexes}
84-
/>
85-
</div>
64+
<IndexesToolbar
65+
isWritable={isWritable}
66+
isReadonly={isReadonly}
67+
isReadonlyView={isReadonlyView}
68+
errorMessage={error}
69+
localAppRegistry={localAppRegistry}
70+
isRefreshing={isRefreshing}
71+
writeStateDescription={description}
72+
onRefreshIndexes={refreshIndexes}
73+
/>
8674
{!isReadonlyView && !error && (
87-
<div className={indexTableStyles}>
88-
<IndexesTable
89-
indexes={indexes}
90-
canDeleteIndex={isWritable && !isReadonly}
91-
onSortTable={sortIndexes}
92-
onDeleteIndex={deleteIndex}
93-
/>
94-
</div>
75+
<AutoSizer disableWidth>
76+
{({ height }) => (
77+
<IndexesTable
78+
indexes={indexes}
79+
canDeleteIndex={isWritable && !isReadonly}
80+
onSortTable={sortIndexes}
81+
onDeleteIndex={deleteIndex}
82+
// Preserve the (table and card bottom) paddings
83+
scrollHeight={height - 48}
84+
/>
85+
)}
86+
</AutoSizer>
9587
)}
9688
</Card>
9789
);

0 commit comments

Comments
 (0)