Skip to content

Commit 635808e

Browse files
authored
feat(features): Add background color on hover in metadata list view (#1618)
1 parent c64ea22 commit 635808e

File tree

4 files changed

+218
-3
lines changed

4 files changed

+218
-3
lines changed

src/features/metadata-based-view/MetadataBasedItemList.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ import './MetadataBasedItemList.scss';
1313
import type { FlattenedMetadataQueryResponseCollection } from '../../common/types/metadataQueries';
1414

1515
const FILE_ICON_SIZE = 32;
16-
const FILE_ICON_COLUMN_WIDTH = 52;
16+
const FILE_ICON_COLUMN_WIDTH = 54;
1717
const FILENAME_COLUMN_WIDTH = 350;
1818
const MIN_METADATA_COLUMN_WIDTH = 250;
1919
const FIXED_COLUMNS_NUMBER = 2; // 2 Sticky columns - 1. file-icon, 2. file-name for each row
2020
const FIXED_ROW_NUMBER = 1; // Header row
2121

22+
type State = {
23+
hoveredRowIndex: number,
24+
};
25+
2226
type Props = {
2327
currentCollection: FlattenedMetadataQueryResponseCollection,
2428
intl: IntlShape,
@@ -34,9 +38,17 @@ type CellRendererArgs = {
3438

3539
type ColumnWidthCallback = ({ index: number }) => number;
3640

37-
class MetadataBasedItemList extends React.Component<Props> {
41+
class MetadataBasedItemList extends React.Component<Props, State> {
3842
props: Props;
3943

44+
constructor(props: Props) {
45+
super(props);
46+
47+
this.state = {
48+
hoveredRowIndex: -1, // initial MultiGrid load
49+
};
50+
}
51+
4052
getColumnWidth(width: number): ColumnWidthCallback {
4153
const { metadataColumnsToShow }: Props = this.props;
4254

@@ -94,14 +106,27 @@ class MetadataBasedItemList extends React.Component<Props> {
94106
return headerData;
95107
}
96108

109+
handleMouseEnter = (rowIndex: number): void => this.setState({ hoveredRowIndex: rowIndex });
110+
111+
handleMouseLeave = (): void => this.setState({ hoveredRowIndex: -1 });
112+
97113
cellRenderer = ({ columnIndex, rowIndex, key, style }: CellRendererArgs): Element<'div'> => {
114+
const { hoveredRowIndex } = this.state;
98115
const data = rowIndex === 0 ? this.getGridHeaderData(columnIndex) : this.getGridCellData(columnIndex, rowIndex);
99116
const classes = classNames('bdl-MetadataBasedItemList-cell', {
117+
'bdl-MetadataBasedItemList-cell--fileIcon': rowIndex > 0 && columnIndex === 0, // file icon cell
100118
'bdl-MetadataBasedItemList-cell--filename': columnIndex === 1, // file name cell
119+
'bdl-MetadataBasedItemList-cell--hover': rowIndex > 0 && rowIndex === hoveredRowIndex,
101120
});
102121

103122
return (
104-
<div key={key} className={classes} style={style}>
123+
<div
124+
key={key}
125+
className={classes}
126+
style={style}
127+
onMouseLeave={this.handleMouseLeave}
128+
onMouseEnter={() => this.handleMouseEnter(rowIndex)}
129+
>
105130
{data}
106131
</div>
107132
);
@@ -135,4 +160,5 @@ class MetadataBasedItemList extends React.Component<Props> {
135160
}
136161
}
137162

163+
export { MetadataBasedItemList as MetadataBasedItemListComponent };
138164
export default injectIntl(MetadataBasedItemList);

src/features/metadata-based-view/MetadataBasedItemList.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,15 @@
2222
.bdl-MetadataBasedItemList-cell--filename {
2323
color: $bdl-gray;
2424
}
25+
26+
.bdl-MetadataBasedItemList-cell--hover {
27+
background-color: $bdl-light-blue-10;
28+
}
29+
30+
.bdl-MetadataBasedItemList-cell--fileIcon {
31+
border-left: 2px solid transparent;
32+
33+
&.bdl-MetadataBasedItemList-cell--hover {
34+
border-left-color: $bdl-dark-blue;
35+
}
36+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import * as React from 'react';
2+
import FileIcon from '../../../icons/file-icon';
3+
4+
import { MetadataBasedItemListComponent as MetadataBasedItemList } from '../MetadataBasedItemList';
5+
6+
jest.mock('react-virtualized/dist/es/AutoSizer', () => () => 'AutoSizer');
7+
8+
describe('features/metadata-based-view/MetadataBasedItemList', () => {
9+
let wrapper;
10+
let instance;
11+
const intl = { formatMessage: jest.fn().mockReturnValue('Name') };
12+
const currentCollection = {
13+
items: [
14+
{
15+
metadata: {
16+
data: { type: 'bill', amount: 500 },
17+
},
18+
name: 'name1.pdf',
19+
},
20+
{
21+
metadata: {
22+
data: { type: 'receipt', amount: 200 },
23+
},
24+
name: 'name2.mp4',
25+
},
26+
],
27+
nextMarker: 'abc',
28+
};
29+
const metadataColumnsToShow = ['type', 'amount'];
30+
const pdfIcon = <FileIcon dimension={32} extension="pdf" />;
31+
const mp4Icon = <FileIcon dimension={32} extension="mp4" />;
32+
33+
const defaultProps = {
34+
currentCollection,
35+
metadataColumnsToShow,
36+
intl,
37+
};
38+
39+
const getWrapper = (props = defaultProps) => mount(<MetadataBasedItemList {...props} />);
40+
41+
beforeEach(() => {
42+
wrapper = getWrapper();
43+
instance = wrapper.instance();
44+
});
45+
46+
describe('getColumnWidth(columnIndex)', () => {
47+
test.each`
48+
columnIndex | columnWidth | desc
49+
${0} | ${54} | ${'file icon'}
50+
${1} | ${350} | ${'file name'}
51+
${2} | ${250} | ${'metadata column'}
52+
`('getColumnWidth() for $desc', ({ columnIndex, columnWidth }) => {
53+
const availableWidth = 500; // width provided to AutoSizer Component
54+
const getWidth = instance.getColumnWidth(availableWidth);
55+
expect(getWidth({ index: columnIndex })).toEqual(columnWidth);
56+
});
57+
});
58+
59+
describe('getGridCellData(columnIndex, rowIndex)', () => {
60+
test.each`
61+
columnIndex | rowIndex | cellData
62+
${0} | ${1} | ${pdfIcon}
63+
${1} | ${1} | ${'name1.pdf'}
64+
${2} | ${1} | ${'bill'}
65+
${3} | ${1} | ${500}
66+
${0} | ${2} | ${mp4Icon}
67+
${1} | ${2} | ${'name2.mp4'}
68+
${2} | ${2} | ${'receipt'}
69+
${3} | ${2} | ${200}
70+
`('cellData for row: $rowIndex, column: $columnIndex', ({ columnIndex, rowIndex, cellData }) => {
71+
const data = instance.getGridCellData(columnIndex, rowIndex);
72+
expect(data).toEqual(cellData);
73+
});
74+
});
75+
76+
describe('getGridHeaderData(columnIndex)', () => {
77+
test.each`
78+
columnIndex | headerData
79+
${0} | ${undefined}
80+
${1} | ${'Name'}
81+
${2} | ${'type'}
82+
${3} | ${'amount'}
83+
`('headerData for column $columnIndex', ({ columnIndex, headerData }) => {
84+
const data = instance.getGridHeaderData(columnIndex);
85+
expect(data).toEqual(headerData);
86+
});
87+
});
88+
89+
describe('handleMouseEnter()', () => {
90+
test('should handle mouse over event by setting state accordingly', () => {
91+
instance.handleMouseEnter(5);
92+
expect(instance.state.hoveredRowIndex).toEqual(5);
93+
});
94+
});
95+
96+
describe('handleMouseLeave()', () => {
97+
test('should handle mouse leave event by setting state accordingly', () => {
98+
instance.handleMouseLeave();
99+
expect(instance.state.hoveredRowIndex).toEqual(-1);
100+
});
101+
});
102+
103+
describe('cellRenderer()', () => {
104+
test.each([
105+
[{ columnIndex: 0, rowIndex: 2, key: 'key', style: {} }, true, false],
106+
[{ columnIndex: 1, rowIndex: 2, key: 'key', style: {} }, false, true],
107+
])('should have correct class names', (arg, hasFileIconClass, hasFileNameClass) => {
108+
const cell = shallow(instance.cellRenderer(arg));
109+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell')).toEqual(true);
110+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--fileIcon')).toEqual(hasFileIconClass);
111+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--filename')).toEqual(hasFileNameClass);
112+
});
113+
114+
test('should have hovered class for adding background color on row hover', () => {
115+
const hoverCellIndex = 1;
116+
instance.handleMouseEnter(hoverCellIndex); // Hover over row
117+
118+
const cell = shallow(
119+
instance.cellRenderer({ columnIndex: 0, rowIndex: hoverCellIndex, key: 'key', style: {} }),
120+
);
121+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--hover')).toEqual(true);
122+
});
123+
});
124+
125+
describe('render()', () => {
126+
test('should render a default component correctly', () => {
127+
expect(wrapper.find('AutoSizer')).toBeTruthy();
128+
expect(wrapper).toMatchSnapshot();
129+
});
130+
});
131+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`features/metadata-based-view/MetadataBasedItemList render() should render a default component correctly 1`] = `
4+
<MetadataBasedItemList
5+
currentCollection={
6+
Object {
7+
"items": Array [
8+
Object {
9+
"metadata": Object {
10+
"data": Object {
11+
"amount": 500,
12+
"type": "bill",
13+
},
14+
},
15+
"name": "name1.pdf",
16+
},
17+
Object {
18+
"metadata": Object {
19+
"data": Object {
20+
"amount": 200,
21+
"type": "receipt",
22+
},
23+
},
24+
"name": "name2.mp4",
25+
},
26+
],
27+
"nextMarker": "abc",
28+
}
29+
}
30+
intl={
31+
Object {
32+
"formatMessage": [MockFunction],
33+
}
34+
}
35+
metadataColumnsToShow={
36+
Array [
37+
"type",
38+
"amount",
39+
]
40+
}
41+
>
42+
<Component>
43+
AutoSizer
44+
</Component>
45+
</MetadataBasedItemList>
46+
`;

0 commit comments

Comments
 (0)