Skip to content

Commit 619e7be

Browse files
author
Mingze
authored
feat(reply): Integrate collaborators endpoint (#484)
1 parent 56331b9 commit 619e7be

20 files changed

+688
-55
lines changed

docs/styles.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
#preview-container {
2828
width: 100vw;
2929
height: 75vh;
30-
}
30+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.ba {
2+
.ba-ItemList-row {
3+
padding: 5px 30px 5px 15px;
4+
}
5+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { SyntheticEvent } from 'react';
2+
import noop from 'lodash/noop';
3+
import ItemRow from './ItemRow';
4+
import './ItemList.scss';
5+
6+
export type Props<T extends { id: string }> = {
7+
activeItemIndex?: number;
8+
itemRowAs?: JSX.Element;
9+
items: T[];
10+
onActivate?: (index: number) => void;
11+
onSelect: (index: number, event: React.SyntheticEvent) => void;
12+
};
13+
14+
const ItemList = <T extends { id: string }>({
15+
activeItemIndex = 0,
16+
itemRowAs = <ItemRow />,
17+
items,
18+
onActivate = noop,
19+
onSelect,
20+
...rest
21+
}: Props<T>): JSX.Element => (
22+
<ul data-testid="ba-ItemList" role="listbox" {...rest}>
23+
{items.map((item, index) =>
24+
React.cloneElement(itemRowAs, {
25+
...item,
26+
key: item.id,
27+
className: 'ba-ItemList-row',
28+
isActive: index === activeItemIndex,
29+
onClick: (event: SyntheticEvent) => {
30+
onSelect(index, event);
31+
},
32+
/* preventDefault on mousedown so blur doesn't happen before click */
33+
onMouseDown: (event: SyntheticEvent) => {
34+
event.preventDefault();
35+
},
36+
onMouseEnter: () => {
37+
onActivate(index);
38+
},
39+
}),
40+
)}
41+
</ul>
42+
);
43+
44+
export default ItemList;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@import '~box-ui-elements/es/styles/variables';
2+
3+
.ba-ItemRow-name {
4+
line-height: 15px;
5+
}
6+
7+
.ba-ItemRow-email {
8+
color: $bdl-gray-62;
9+
font-size: 11px;
10+
line-height: 15px;
11+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import DatalistItem from 'box-ui-elements/es/components/datalist-item';
3+
import { UserMini, GroupMini } from '../../@types';
4+
import './ItemRow.scss';
5+
6+
export type Props = {
7+
id?: string;
8+
item?: UserMini | GroupMini;
9+
name?: string;
10+
};
11+
12+
const ItemRow = ({ item, ...rest }: Props): JSX.Element | null => {
13+
if (!item || !item.name) {
14+
return null;
15+
}
16+
17+
return (
18+
<DatalistItem {...rest}>
19+
<div className="ba-ItemRow-name" data-testid="ba-ItemRow-name">
20+
{item.name}
21+
</div>
22+
{'email' in item && (
23+
<div className="ba-ItemRow-email" data-testid="ba-ItemRow-email">
24+
{item.email}
25+
</div>
26+
)}
27+
</DatalistItem>
28+
);
29+
};
30+
31+
export default ItemRow;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import { shallow, ShallowWrapper } from 'enzyme';
3+
import ItemList, { Props } from '../ItemList';
4+
import { Collaborator } from '../../../@types';
5+
6+
describe('components/ItemList', () => {
7+
const defaults: Props<Collaborator> = {
8+
items: [
9+
{ id: 'testid1', name: 'test1' },
10+
{ id: 'testid2', name: 'test2' },
11+
],
12+
onSelect: jest.fn(),
13+
};
14+
15+
const getWrapper = (props = {}): ShallowWrapper => shallow(<ItemList {...defaults} {...props} />);
16+
17+
describe('render()', () => {
18+
test('should render elements with correct props', () => {
19+
const wrapper = getWrapper({ activeItemIndex: 1 });
20+
21+
const itemList = wrapper.find('[data-testid="ba-ItemList"]');
22+
const firstChild = itemList.childAt(0);
23+
const secondChild = itemList.childAt(1);
24+
25+
expect(itemList.props()).toMatchObject({
26+
role: 'listbox',
27+
});
28+
expect(itemList.children()).toHaveLength(2);
29+
expect(firstChild.prop('className')).toEqual('ba-ItemList-row');
30+
expect(firstChild.prop('isActive')).toEqual(false);
31+
expect(secondChild.prop('isActive')).toEqual(true);
32+
});
33+
34+
test('should trigger events', () => {
35+
const mockSetIndex = jest.fn();
36+
const mockEvent = {
37+
preventDefault: jest.fn(),
38+
};
39+
40+
const wrapper = getWrapper({ onActivate: mockSetIndex });
41+
const firstChild = wrapper.find('[data-testid="ba-ItemList"]').childAt(0);
42+
43+
firstChild.simulate('click', mockEvent);
44+
expect(defaults.onSelect).toBeCalledWith(0, mockEvent);
45+
46+
firstChild.simulate('mousedown', mockEvent);
47+
expect(mockEvent.preventDefault).toBeCalled();
48+
49+
firstChild.simulate('mouseenter');
50+
expect(mockSetIndex).toBeCalledWith(0);
51+
});
52+
});
53+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
import { shallow, ShallowWrapper } from 'enzyme';
3+
import DatalistItem from 'box-ui-elements/es/components/datalist-item';
4+
import ItemRow, { Props } from '../ItemRow';
5+
6+
describe('components/Popups/ReplyField/ItemRow', () => {
7+
const defaults: Props = {
8+
id: 'testid',
9+
item: { email: 'test@box.com', id: 'testid', name: 'testname', type: 'user' },
10+
name: 'testname',
11+
};
12+
13+
const getWrapper = (props = {}): ShallowWrapper => shallow(<ItemRow {...defaults} {...props} />);
14+
15+
describe('render()', () => {
16+
test('should render DatalistItem with correct props', () => {
17+
const wrapper = getWrapper();
18+
19+
expect(wrapper.find(DatalistItem).props()).toMatchObject({
20+
id: 'testid',
21+
name: 'testname',
22+
});
23+
});
24+
25+
test('should not render anything if no item', () => {
26+
const wrapper = getWrapper({ item: null });
27+
28+
expect(wrapper.exists(DatalistItem)).toBeFalsy();
29+
});
30+
31+
test('should not render anything if no item name', () => {
32+
const wrapper = getWrapper({ item: {} });
33+
34+
expect(wrapper.exists(DatalistItem)).toBeFalsy();
35+
});
36+
37+
test('should render item name and email', () => {
38+
const wrapper = getWrapper();
39+
40+
expect(wrapper.find('[data-testid="ba-ItemRow-name"]').text()).toBe('testname');
41+
expect(wrapper.find('[data-testid="ba-ItemRow-email"]').text()).toBe('test@box.com');
42+
});
43+
44+
test('should not render email if item has no email', () => {
45+
const wrapper = getWrapper({ item: { id: 'testid', name: 'testname', type: 'group' } });
46+
47+
expect(wrapper.exists('[data-testid="ba-ItemRow-email"]')).toBeFalsy();
48+
});
49+
});
50+
});

src/components/ItemList/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ItemList';

src/components/Popups/Popper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import unionBy from 'lodash/unionBy';
44

55
export type Instance = popper.Instance;
66
export type Options = popper.Options;
7+
export type State = popper.State;
78
export type VirtualElement = popper.VirtualElement;
89

910
export type PopupReference = Element | VirtualElement;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@import '~box-ui-elements/es/styles/variables';
2+
3+
.ba-MentionItem-link {
4+
color: $bdl-box-blue;
5+
font-weight: bold;
6+
}

0 commit comments

Comments
 (0)