Skip to content

Commit e545817

Browse files
committed
Merge branch 'main' of https://github.com/mongodb-js/compass into COMPASS-9311-dm-grid-view
2 parents 0cf7271 + ebec8b9 commit e545817

File tree

16 files changed

+619
-192
lines changed

16 files changed

+619
-192
lines changed

packages/compass-components/src/components/empty-content.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,32 +44,45 @@ const callToActionLinkContainerStyles = css({
4444
});
4545

4646
type EmptyContentProps = {
47-
icon: React.FunctionComponent;
47+
icon?: React.FunctionComponent;
4848
title: string;
49+
titleClassName?: string;
4950
subTitle: React.ReactNode;
51+
subTitleClassName?: string;
5052
callToAction?: React.ReactNode;
5153
callToActionLink?: React.ReactNode;
5254
};
5355

5456
const EmptyContent: React.FunctionComponent<
5557
EmptyContentProps & React.HTMLProps<HTMLDivElement>
56-
> = ({ icon: Icon, title, subTitle, callToAction, callToActionLink }) => {
58+
> = ({
59+
icon: Icon,
60+
title,
61+
subTitle,
62+
callToAction,
63+
callToActionLink,
64+
titleClassName,
65+
subTitleClassName,
66+
}) => {
5767
const darkMode = useDarkMode();
5868

5969
return (
6070
<div data-testid="empty-content" className={containerStyles}>
61-
<div className={iconStyles}>
62-
<Icon />
63-
</div>
71+
{Icon && (
72+
<div className={iconStyles}>
73+
<Icon />
74+
</div>
75+
)}
6476
<Subtitle
6577
className={cx(
6678
titleStyles,
67-
darkMode ? titleDarkStyles : titleLightStyles
79+
darkMode ? titleDarkStyles : titleLightStyles,
80+
titleClassName
6881
)}
6982
>
7083
{title}
7184
</Subtitle>
72-
<Body className={subTitleStyles}>{subTitle}</Body>
85+
<Body className={cx(subTitleStyles, subTitleClassName)}>{subTitle}</Body>
7386
{!!callToAction && (
7487
<div className={callToActionStyles}>
7588
{typeof callToAction === 'string' ? (

packages/compass-components/src/components/select-table.spec.tsx renamed to packages/compass-components/src/components/select-list.spec.tsx

Lines changed: 23 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { expect } from 'chai';
88
import React from 'react';
99
import sinon from 'sinon';
10-
import { SelectTable } from './select-table';
10+
import { SelectList } from './select-list';
1111
import { cloneDeep } from 'lodash';
1212

1313
type TestItem = {
@@ -17,25 +17,17 @@ type TestItem = {
1717
col2: string;
1818
};
1919

20-
describe('SelectTable', function () {
20+
describe('SelectList', function () {
2121
let items: TestItem[];
22-
let columns: [key: keyof TestItem, label: string | JSX.Element][];
22+
let label: { displayLabelKey: keyof TestItem; name: string | JSX.Element };
2323
let onChange: sinon.SinonStub;
2424

2525
beforeEach(function () {
2626
items = [
2727
{ id: 'id1', selected: true, col1: '1x1', col2: '1x2' },
2828
{ id: 'id2', selected: true, col1: '2x1', col2: '2x2' },
2929
];
30-
columns = [
31-
['col1', 'Column1'],
32-
[
33-
'col2',
34-
<span key="" data-testid="column2-span">
35-
Column2
36-
</span>,
37-
],
38-
];
30+
label = { displayLabelKey: 'col1', name: 'Column1' };
3931
onChange = sinon.stub();
4032
});
4133

@@ -44,28 +36,21 @@ describe('SelectTable', function () {
4436
});
4537

4638
describe('render', function () {
47-
it('allows listing multiple selectable items in a table', function () {
48-
render(
49-
<SelectTable items={items} columns={columns} onChange={onChange} />
50-
);
51-
52-
expect(screen.getByTestId('column2-span')).to.be.visible;
53-
expect(screen.getByTestId('item-id1-col1')).to.have.text('1x1');
54-
expect(screen.getByTestId('item-id1-col2')).to.have.text('1x2');
39+
it('allows listing multiple selectable items in the list', function () {
40+
render(<SelectList items={items} label={label} onChange={onChange} />);
41+
42+
expect(screen.getByLabelText('1x1')).to.be.visible;
5543
});
5644

5745
it('renders checkboxes as expected when all items are selected', function () {
58-
render(
59-
<SelectTable items={items} columns={columns} onChange={onChange} />
60-
);
46+
render(<SelectList items={items} label={label} onChange={onChange} />);
6147

6248
expect(
63-
screen.getByTestId('select-table-all-checkbox').closest('input')
64-
?.checked
49+
screen.getByTestId('select-list-all-checkbox').closest('input')?.checked
6550
).to.equal(true);
6651
expect(
6752
screen
68-
.getByTestId('select-table-all-checkbox')
53+
.getByTestId('select-list-all-checkbox')
6954
.closest('input')
7055
?.getAttribute('aria-checked')
7156
).to.equal('true');
@@ -80,17 +65,14 @@ describe('SelectTable', function () {
8065
it('renders checkboxes as expected when no items are selected', function () {
8166
items[0].selected = false;
8267
items[1].selected = false;
83-
render(
84-
<SelectTable items={items} columns={columns} onChange={onChange} />
85-
);
68+
render(<SelectList items={items} label={label} onChange={onChange} />);
8669

8770
expect(
88-
screen.getByTestId('select-table-all-checkbox').closest('input')
89-
?.checked
71+
screen.getByTestId('select-list-all-checkbox').closest('input')?.checked
9072
).to.equal(false);
9173
expect(
9274
screen
93-
.getByTestId('select-table-all-checkbox')
75+
.getByTestId('select-list-all-checkbox')
9476
.closest('input')
9577
?.getAttribute('aria-checked')
9678
).to.equal('false');
@@ -104,17 +86,14 @@ describe('SelectTable', function () {
10486

10587
it('renders checkboxes as expected when some items are selected', function () {
10688
items[0].selected = false;
107-
render(
108-
<SelectTable items={items} columns={columns} onChange={onChange} />
109-
);
89+
render(<SelectList items={items} label={label} onChange={onChange} />);
11090

11191
expect(
112-
screen.getByTestId('select-table-all-checkbox').closest('input')
113-
?.checked
92+
screen.getByTestId('select-list-all-checkbox').closest('input')?.checked
11493
).to.equal(false);
11594
expect(
11695
screen
117-
.getByTestId('select-table-all-checkbox')
96+
.getByTestId('select-list-all-checkbox')
11897
.closest('input')
11998
?.getAttribute('aria-checked')
12099
).to.equal('mixed');
@@ -131,9 +110,7 @@ describe('SelectTable', function () {
131110
it('calls onChange when a single item is selected', function () {
132111
const originalItems = cloneDeep(items);
133112
items[0].selected = false;
134-
render(
135-
<SelectTable items={items} columns={columns} onChange={onChange} />
136-
);
113+
render(<SelectList items={items} label={label} onChange={onChange} />);
137114

138115
fireEvent.click(screen.getByTestId('select-id1'));
139116
expect(onChange).to.have.been.calledWith(originalItems);
@@ -142,9 +119,7 @@ describe('SelectTable', function () {
142119
it('calls onChange when a single item is deselected', function () {
143120
const expectedItems = cloneDeep(items);
144121
items[0].selected = false;
145-
render(
146-
<SelectTable items={items} columns={columns} onChange={onChange} />
147-
);
122+
render(<SelectList items={items} label={label} onChange={onChange} />);
148123

149124
fireEvent.click(screen.getByTestId('select-id1'));
150125
expect(onChange).to.have.been.calledWith(expectedItems);
@@ -154,23 +129,19 @@ describe('SelectTable', function () {
154129
const originalItems = cloneDeep(items);
155130
items[0].selected = false;
156131
items[1].selected = false;
157-
render(
158-
<SelectTable items={items} columns={columns} onChange={onChange} />
159-
);
132+
render(<SelectList items={items} label={label} onChange={onChange} />);
160133

161-
fireEvent.click(screen.getByTestId('select-table-all-checkbox'));
134+
fireEvent.click(screen.getByTestId('select-list-all-checkbox'));
162135
expect(onChange).to.have.been.calledWith(originalItems);
163136
});
164137

165138
it('calls onChange when all items are deselected', function () {
166139
const expectedItems = cloneDeep(items);
167140
items[0].selected = false;
168141
items[1].selected = false;
169-
render(
170-
<SelectTable items={items} columns={columns} onChange={onChange} />
171-
);
142+
render(<SelectList items={items} label={label} onChange={onChange} />);
172143

173-
fireEvent.click(screen.getByTestId('select-table-all-checkbox'));
144+
fireEvent.click(screen.getByTestId('select-list-all-checkbox'));
174145
expect(onChange).to.have.been.calledWith(expectedItems);
175146
});
176147
});
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { useCallback } from 'react';
2+
import { Checkbox } from './leafygreen';
3+
import { spacing } from '@leafygreen-ui/tokens';
4+
import { css, cx } from '@leafygreen-ui/emotion';
5+
import { palette } from '@leafygreen-ui/palette';
6+
import { useDarkMode } from '../hooks/use-theme';
7+
8+
const checkboxStyles = css({
9+
padding: spacing[100],
10+
});
11+
12+
const containerStyles = css({
13+
display: 'flex',
14+
flexDirection: 'column',
15+
});
16+
17+
const evenRowStylesDark = css({ backgroundColor: palette.gray.dark3 });
18+
const evenRowStylesLight = css({ backgroundColor: palette.gray.light3 });
19+
20+
const listHeaderStyles = css({
21+
display: 'flex',
22+
alignItems: 'center',
23+
fontWeight: 600,
24+
borderBottom: `${spacing[100]}px solid ${palette.gray.light2}`,
25+
flexShrink: 0,
26+
padding: `${spacing[100]}px 0px`,
27+
});
28+
const listBodyStyles = css({
29+
overflow: 'auto',
30+
});
31+
const listItemStyles = css({
32+
padding: `${spacing[100]}px 0px`,
33+
});
34+
35+
const selectAllLabelStyles = css({ lineHeight: '16px' });
36+
37+
type SelectItem = {
38+
id: string;
39+
selected: boolean;
40+
};
41+
42+
type SelectListProps<T extends SelectItem> = {
43+
items: T[];
44+
label: {
45+
displayLabelKey: string & keyof T;
46+
ariaLabelKey?: string & keyof T;
47+
name: string | JSX.Element;
48+
};
49+
onChange: (newList: T[]) => void;
50+
disabled?: boolean;
51+
className?: string;
52+
};
53+
54+
export function SelectList<T extends SelectItem>(
55+
props: SelectListProps<T>
56+
): React.ReactElement {
57+
const { items, label, disabled, onChange } = props;
58+
59+
const isDarkMode = useDarkMode();
60+
const evenRowStyles = isDarkMode ? evenRowStylesDark : evenRowStylesLight;
61+
62+
const selectAll = items.every((item) => item.selected);
63+
const selectNone = items.every((item) => !item.selected);
64+
65+
const handleSelectAllChange = useCallback(
66+
(e: React.ChangeEvent<HTMLInputElement>) => {
67+
onChange(
68+
items.map((item) => ({ ...item, selected: !!e.target.checked }))
69+
);
70+
},
71+
[items, onChange]
72+
);
73+
const handleSelectItemChange = useCallback(
74+
(e: React.ChangeEvent<HTMLInputElement>) => {
75+
onChange(
76+
items.map((item) =>
77+
e.target.name === `select-${item.id}`
78+
? { ...item, selected: !!e.target.checked }
79+
: item
80+
)
81+
);
82+
},
83+
[items, onChange]
84+
);
85+
86+
return (
87+
<div className={cx(props.className, containerStyles)}>
88+
<div className={listHeaderStyles}>
89+
<Checkbox
90+
className={cx(checkboxStyles, css({ paddingRight: 0 }))}
91+
data-testid="select-list-all-checkbox"
92+
aria-label="Select all"
93+
onChange={handleSelectAllChange}
94+
checked={selectAll}
95+
indeterminate={!selectAll && !selectNone}
96+
disabled={disabled}
97+
/>
98+
<div className={selectAllLabelStyles}>{label.name}</div>
99+
</div>
100+
<div className={listBodyStyles}>
101+
{items.map((item, index) => (
102+
<div
103+
className={cx(listItemStyles, index % 2 === 0 && evenRowStyles)}
104+
key={`select-list-item-${item.id}`}
105+
data-testid={`select-list-item-${item.id}`}
106+
>
107+
<Checkbox
108+
className={checkboxStyles}
109+
key={`select-${item.id}`}
110+
name={`select-${item.id}`}
111+
data-testid={`select-${item.id}`}
112+
label={item[label.displayLabelKey]}
113+
aria-label={
114+
item[label.ariaLabelKey ?? label.displayLabelKey] as string
115+
}
116+
onChange={handleSelectItemChange}
117+
checked={item.selected}
118+
disabled={disabled}
119+
/>
120+
</div>
121+
))}
122+
</div>
123+
</div>
124+
);
125+
}

0 commit comments

Comments
 (0)