Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reply): Integrate collaborators endpoint #484

Merged
merged 1 commit into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
#preview-container {
width: 100vw;
height: 75vh;
}
}
5 changes: 5 additions & 0 deletions src/components/ItemList/ItemList.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.ba {
.ba-ItemList-row {
padding: 5px 30px 5px 15px;
}
}
44 changes: 44 additions & 0 deletions src/components/ItemList/ItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { SyntheticEvent } from 'react';
import noop from 'lodash/noop';
import ItemRow from './ItemRow';
import './ItemList.scss';

export type Props<T extends { id: string }> = {
activeItemIndex?: number;
itemRowAs?: JSX.Element;
items: T[];
onActivate?: (index: number) => void;
onSelect: (index: number, event: React.SyntheticEvent) => void;
};

const ItemList = <T extends { id: string }>({
activeItemIndex = 0,
itemRowAs = <ItemRow />,
items,
onActivate = noop,
onSelect,
...rest
}: Props<T>): JSX.Element => (
<ul data-testid="ba-ItemList" role="listbox" {...rest}>
{items.map((item, index) =>
React.cloneElement(itemRowAs, {
...item,
key: item.id,
className: 'ba-ItemList-row',
isActive: index === activeItemIndex,
onClick: (event: SyntheticEvent) => {
onSelect(index, event);
},
/* preventDefault on mousedown so blur doesn't happen before click */
onMouseDown: (event: SyntheticEvent) => {
event.preventDefault();
},
onMouseEnter: () => {
onActivate(index);
},
}),
)}
</ul>
);

export default ItemList;
11 changes: 11 additions & 0 deletions src/components/ItemList/ItemRow.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@import '~box-ui-elements/es/styles/variables';

.ba-ItemRow-name {
line-height: 15px;
}

.ba-ItemRow-email {
color: $bdl-gray-62;
font-size: 11px;
line-height: 15px;
}
31 changes: 31 additions & 0 deletions src/components/ItemList/ItemRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import DatalistItem from 'box-ui-elements/es/components/datalist-item';
import { UserMini, GroupMini } from '../../@types';
import './ItemRow.scss';

export type Props = {
id?: string;
item?: UserMini | GroupMini;
name?: string;
};

const ItemRow = ({ item, ...rest }: Props): JSX.Element | null => {
if (!item || !item.name) {
return null;
}

return (
<DatalistItem {...rest}>
<div className="ba-ItemRow-name" data-testid="ba-ItemRow-name">
{item.name}
</div>
{'email' in item && (
<div className="ba-ItemRow-email" data-testid="ba-ItemRow-email">
{item.email}
</div>
)}
</DatalistItem>
);
};

export default ItemRow;
53 changes: 53 additions & 0 deletions src/components/ItemList/__tests__/ItemList-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import ItemList, { Props } from '../ItemList';
import { Collaborator } from '../../../@types';

describe('components/ItemList', () => {
const defaults: Props<Collaborator> = {
items: [
{ id: 'testid1', name: 'test1' },
{ id: 'testid2', name: 'test2' },
],
onSelect: jest.fn(),
};

const getWrapper = (props = {}): ShallowWrapper => shallow(<ItemList {...defaults} {...props} />);

describe('render()', () => {
test('should render elements with correct props', () => {
const wrapper = getWrapper({ activeItemIndex: 1 });

const itemList = wrapper.find('[data-testid="ba-ItemList"]');
const firstChild = itemList.childAt(0);
const secondChild = itemList.childAt(1);

expect(itemList.props()).toMatchObject({
role: 'listbox',
});
expect(itemList.children()).toHaveLength(2);
expect(firstChild.prop('className')).toEqual('ba-ItemList-row');
expect(firstChild.prop('isActive')).toEqual(false);
expect(secondChild.prop('isActive')).toEqual(true);
});

test('should trigger events', () => {
const mockSetIndex = jest.fn();
const mockEvent = {
preventDefault: jest.fn(),
};

const wrapper = getWrapper({ onActivate: mockSetIndex });
const firstChild = wrapper.find('[data-testid="ba-ItemList"]').childAt(0);

firstChild.simulate('click', mockEvent);
expect(defaults.onSelect).toBeCalledWith(0, mockEvent);

firstChild.simulate('mousedown', mockEvent);
expect(mockEvent.preventDefault).toBeCalled();

firstChild.simulate('mouseenter');
expect(mockSetIndex).toBeCalledWith(0);
});
});
});
50 changes: 50 additions & 0 deletions src/components/ItemList/__tests__/ItemRow-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import DatalistItem from 'box-ui-elements/es/components/datalist-item';
import ItemRow, { Props } from '../ItemRow';

describe('components/Popups/ReplyField/ItemRow', () => {
const defaults: Props = {
id: 'testid',
item: { email: '[email protected]', id: 'testid', name: 'testname', type: 'user' },
name: 'testname',
};

const getWrapper = (props = {}): ShallowWrapper => shallow(<ItemRow {...defaults} {...props} />);

describe('render()', () => {
test('should render DatalistItem with correct props', () => {
const wrapper = getWrapper();

expect(wrapper.find(DatalistItem).props()).toMatchObject({
id: 'testid',
name: 'testname',
});
});

test('should not render anything if no item', () => {
const wrapper = getWrapper({ item: null });

expect(wrapper.exists(DatalistItem)).toBeFalsy();
});

test('should not render anything if no item name', () => {
const wrapper = getWrapper({ item: {} });

expect(wrapper.exists(DatalistItem)).toBeFalsy();
});

test('should render item name and email', () => {
const wrapper = getWrapper();

expect(wrapper.find('[data-testid="ba-ItemRow-name"]').text()).toBe('testname');
expect(wrapper.find('[data-testid="ba-ItemRow-email"]').text()).toBe('[email protected]');
});

test('should not render email if item has no email', () => {
const wrapper = getWrapper({ item: { id: 'testid', name: 'testname', type: 'group' } });

expect(wrapper.exists('[data-testid="ba-ItemRow-email"]')).toBeFalsy();
});
});
});
1 change: 1 addition & 0 deletions src/components/ItemList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ItemList';
1 change: 1 addition & 0 deletions src/components/Popups/Popper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import unionBy from 'lodash/unionBy';

export type Instance = popper.Instance;
export type Options = popper.Options;
export type State = popper.State;
export type VirtualElement = popper.VirtualElement;

export type PopupReference = Element | VirtualElement;
Expand Down
6 changes: 6 additions & 0 deletions src/components/Popups/ReplyField/MentionItem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import '~box-ui-elements/es/styles/variables';

.ba-MentionItem-link {
color: $bdl-box-blue;
font-weight: bold;
}
25 changes: 25 additions & 0 deletions src/components/Popups/ReplyField/MentionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';
import { ContentState } from 'draft-js';
import './MentionItem.scss';

export type Props = {
children: React.ReactNode;
contentState: ContentState;
entityKey: string;
};

const MentionItem = ({ contentState, entityKey, children }: Props): JSX.Element => {
const { id } = contentState.getEntity(entityKey).getData();

return id ? (
<a className="ba-MentionItem-link" data-testid="ba-MentionItem-link" href={`/profile/${id}`}>
{children}
</a>
) : (
<span className="ba-MentionItem-link" data-testid="ba-MentionItem-text">
{children}
</span>
);
};

export default MentionItem;
mxiao6 marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 4 additions & 6 deletions src/components/Popups/ReplyField/PopupList.scss
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
@import '~box-ui-elements/es/styles/variables';

.ba-PopupList {
font-size: 13px;
@include common-typography;

.ba-Popup-arrow {
display: none;
}

.ba-Popup-content {
padding-top: 10px;
padding-bottom: 10px;
border: solid 1px $bdl-gray-30;
border-radius: $bdl-border-radius-size;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
}

.ba-PopupList-item {
mxiao6 marked this conversation as resolved.
Show resolved Hide resolved
padding: 10px;
}

.ba-PopupList-prompt {
padding-top: 0;
padding-right: 10px;
padding-left: 10px;
}
}
27 changes: 19 additions & 8 deletions src/components/Popups/ReplyField/PopupList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import * as React from 'react';
import React from 'react';
import isEmpty from 'lodash/isEmpty';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is a new import. Is it needed or can we do !items.length?

import { FormattedMessage } from 'react-intl';
import ItemList from '../../ItemList/ItemList';
import messages from '../messages';
import PopupBase from '../PopupBase';
import { Options, PopupReference } from '../Popper';

import './PopupList.scss';

export type Props = {
export type Props<T extends { id: string }> = {
activeItemIndex?: number;
itemRowAs?: JSX.Element;
items: T[];
onActivate?: (index: number) => void;
onSelect: (index: number, event: React.SyntheticEvent) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the event used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the event used anywhere?

not now, but I think we should provide this info to the parent and parent can choose to use it or not.

options?: Partial<Options>;
reference: PopupReference;
};

Expand All @@ -28,11 +35,15 @@ const options: Partial<Options> = {
placement: 'bottom-start',
};

const PopupList = ({ reference, ...rest }: Props): JSX.Element => (
<PopupBase className="ba-PopupList" options={options} reference={reference} {...rest}>
<div className="ba-PopupList-prompt ba-PopupList-item">
<FormattedMessage {...messages.popupListPrompt} />
</div>
const PopupList = <T extends { id: string }>({ items, reference, ...rest }: Props<T>): JSX.Element => (
<PopupBase className="ba-PopupList" options={options} reference={reference}>
{isEmpty(items) ? (
<div className="ba-PopupList-prompt">
<FormattedMessage {...messages.popupListPrompt} />
</div>
) : (
<ItemList items={items} {...rest} />
)}
</PopupBase>
);

Expand Down
Loading