-
Notifications
You must be signed in to change notification settings - Fork 51
feat(reply): Integrate collaborators endpoint #484
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,4 +27,4 @@ | |
| #preview-container { | ||
| width: 100vw; | ||
| height: 75vh; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .ba { | ||
| .ba-ItemList-row { | ||
| padding: 5px 30px 5px 15px; | ||
| } | ||
| } |
| 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; |
| 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; | ||
| } |
| 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; |
| 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); | ||
| }); | ||
| }); | ||
| }); |
| 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: 'test@box.com', 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('test@box.com'); | ||
| }); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default } from './ItemList'; |
| 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; | ||
| } |
| 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; | ||
| 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 { | ||
mingzexiao6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| padding: 10px; | ||
| } | ||
|
|
||
| .ba-PopupList-prompt { | ||
| padding-top: 0; | ||
| padding-right: 10px; | ||
| padding-left: 10px; | ||
| } | ||
| } | ||
| 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'; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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; | ||
| }; | ||
|
|
||
|
|
@@ -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> | ||
| ); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.