Skip to content

Commit

Permalink
feat(content-explorer-modal-container): add optional info notice (#3634)
Browse files Browse the repository at this point in the history
* feat(content-explorer-modal-container): add optional info notice

* feat(content-explorer-modal-container): address review comments

* feat(content-explorer-modal-container): update prop types

* feat(content-explorer-modal-container): address review comments part 2
  • Loading branch information
psatala-box authored Sep 12, 2024
1 parent a6f4225 commit 20d4c3f
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 55 deletions.
2 changes: 2 additions & 0 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,8 @@ boxui.contentExplorer.filepath = File path
boxui.contentExplorer.folderTreeBreadcrumbsText = {folderName} ({totalItems})
# Label text shown next to the Include Subfolders toggle
boxui.contentExplorer.includeSubfolders = Include Subfolders
# Aria label for the info icon
boxui.contentExplorer.infoNoticeIconAriaLabel = Info icon
# Text shown on button used to move an item to a different folder
boxui.contentExplorer.move = Move
# Text shown as the header for a column of item names in the list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ class ContentExplorerModalContainer extends Component {
searchInputProps: PropTypes.object,
/** Custom text for the choose button */
chooseButtonText: PropTypes.node,
/** Text for the informational notice, defaults to empty string, which makes notice not visible */
infoNoticeText: PropTypes.string,
};

static defaultProps = {
Expand Down Expand Up @@ -207,6 +209,7 @@ class ContentExplorerModalContainer extends Component {
createFolderError,
initialFoldersPath,
shouldNotUsePortal,
infoNoticeText,
...rest
} = this.props;
const { foldersPath, isNewFolderModalOpen } = this.state;
Expand All @@ -223,6 +226,7 @@ class ContentExplorerModalContainer extends Component {
onEnterFolder={this.handleEnterFolder}
onCreateNewFolderButtonClick={this.handleCreateNewFolderButtonClick}
shouldNotUsePortal={shouldNotUsePortal}
infoNoticeText={infoNoticeText}
{...rest}
/>
{isNewFolderModalOpen && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ContentExplorerModalContainer from '../ContentExplorerModalContainer';

describe('features/content-explorer/content-explorer-modal-container/ContentExplorerModalContainer', () => {
const sandbox = sinon.sandbox.create();
const initialSelectedItems = { '123': { id: '123', name: 'folder123' } };
const initialSelectedItems = { 123: { id: '123', name: 'folder123' } };
const renderComponent = (props, renderer = shallow) =>
renderer(
<ContentExplorerModalContainer
Expand Down Expand Up @@ -97,6 +97,12 @@ describe('features/content-explorer/content-explorer-modal-container/ContentExpl

expect(wrapper.find('Portal').length).toBe(0);
});

test('should pass infoNoticeText to ContentExplorerModal', () => {
const infoNoticeText = 'info notice text';
const wrapper = renderComponent({ infoNoticeText });
expect(wrapper.find('ContentExplorerModal').prop('infoNoticeText')).toEqual(infoNoticeText);
});
});

describe('onNewFolderModalShown', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Props = {
onViewSelectedClick?: Function,
shouldNotUsePortal?: boolean,
title?: string,
infoNoticeText?: string,
};

const ContentExplorerModal = ({
Expand All @@ -49,6 +50,7 @@ const ContentExplorerModal = ({
onSelectedClick,
onSelectItem,
shouldNotUsePortal = false,
infoNoticeText = '',
...rest
}: Props) => (
<Modal
Expand All @@ -70,6 +72,7 @@ const ContentExplorerModal = ({
onSelectItem={onSelectItem}
listWidth={560}
listHeight={285}
infoNoticeText={infoNoticeText}
{...rest}
/>
</Modal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,11 @@ describe('features/content-explorer/content-explorer-modal/ContentExplorerModal'
expect(wrapper.find('ContentExplorer').prop('onSelectedClick')).toEqual(onSelectedClick);
expect(wrapper.find('ContentExplorer').prop('onSelectItem')).toEqual(onSelectItem);
});

test('should pass infoNoticeText to ContentExplorer', () => {
const infoNoticeText = 'info notice text';
const wrapper = renderComponent({ infoNoticeText });
expect(wrapper.find('ContentExplorer').prop('infoNoticeText')).toEqual(infoNoticeText);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ exports[`features/content-explorer/content-explorer-modal/ContentExplorerModal r
contentExplorerMode="selectFile"
customInput={[Function]}
formatMessage={[Function]}
infoNoticeText=""
initialFoldersPath={[]}
isResponsive={false}
items={[]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ContentExplorerEmptyState from './ContentExplorerEmptyState';
import ContentExplorerActionButtons from './ContentExplorerActionButtons';
import ContentExplorerSelectAll from './ContentExplorerSelectAll';
import ContentExplorerIncludeSubfolders from './ContentExplorerIncludeSubfolders';
import ContentExplorerInfoNotice from './ContentExplorerInfoNotice';

import ItemList from '../item-list';
import { ContentExplorerModePropType, FoldersPathPropType, ItemsPropType } from '../prop-types';
Expand Down Expand Up @@ -158,6 +159,8 @@ class ContentExplorer extends Component {
listHeight: PropTypes.number.isRequired,
/** Props for the search input */
searchInputProps: PropTypes.object,
/** Text for the informational notice, defaults to empty string, which makes notice not visible */
infoNoticeText: PropTypes.string,
};

static defaultProps = {
Expand Down Expand Up @@ -496,6 +499,7 @@ class ContentExplorer extends Component {
listWidth,
listHeight,
searchInputProps,
infoNoticeText,
...rest
} = this.props;
const { isInSearchMode, foldersPath, isSelectAllChecked } = this.state;
Expand Down Expand Up @@ -552,6 +556,7 @@ class ContentExplorer extends Component {
}}
{...contentExplorerProps}
>
{infoNoticeText && <ContentExplorerInfoNotice infoNoticeText={infoNoticeText} />}
<ContentExplorerHeaderActions
breadcrumbProps={breadcrumbProps}
contentExplorerMode={contentExplorerMode}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';
import { useIntl } from 'react-intl';

import { InlineNotice } from '@box/blueprint-web';

import messages from '../messages';

export interface ContentExplorerInfoNoticeProps {
infoNoticeText: string;
}

const ContentExplorerInfoNotice = ({ infoNoticeText }: ContentExplorerInfoNoticeProps) => {
const { formatMessage } = useIntl();
return (
<InlineNotice variant="info" variantIconAriaLabel={formatMessage(messages.infoNoticeIconAriaLabel)}>
{infoNoticeText}
</InlineNotice>
);
};

export default ContentExplorerInfoNotice;
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {

wrapper.setState({
selectedItems: {
'1': {
1: {
id: '1',
isActionDisabled: true,
name: 'name',
Expand Down Expand Up @@ -216,7 +216,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
{ id: '3', name: 'item3' },
];

const selectedItems = { '1': items[0] };
const selectedItems = { 1: items[0] };
const wrapper = renderComponent({
contentExplorerMode,
items,
Expand Down Expand Up @@ -271,13 +271,31 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {

test('should render all items from both selectedItems state and controlledSelectedItems prop', () => {
const wrapper = renderComponent({
controlledSelectedItems: { '2': { id: '2', name: 'item2' } },
controlledSelectedItems: { 2: { id: '2', name: 'item2' } },
});
const selectedItems = { '1': { id: '1', name: 'item1' } };
const selectedItems = { 1: { id: '1', name: 'item1' } };
wrapper.setState({ selectedItems });
expect(Object.keys(wrapper.find('ItemList').prop('selectedItems')).length).toBe(2);
expect(Object.keys(wrapper.find('ContentExplorerActionButtons').prop('selectedItems')).length).toBe(2);
});

test('should not render ContentExplorerInfoNotice by default', () => {
const wrapper = renderComponent();

expect(wrapper.exists('ContentExplorerInfoNotice')).toBe(false);
});

test('should not render ContentExplorerInfoNotice when info notice text is empty', () => {
const wrapper = renderComponent({ infoNoticeText: '' });

expect(wrapper.exists('ContentExplorerInfoNotice')).toBe(false);
});

test('should render ContentExplorerInfoNotice when info notice text is not empty', () => {
const wrapper = renderComponent({ infoNoticeText: 'text' });

expect(wrapper.exists('ContentExplorerInfoNotice')).toBe(true);
});
});

describe('onEnterFolder', () => {
Expand Down Expand Up @@ -312,10 +330,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
const clickedFolder = items[clickedFolderIndex];
const newFoldersPath = initialFoldersPath.concat([clickedFolder]);

wrapper
.find('.item-list-name')
.first()
.simulate('click');
wrapper.find('.item-list-name').first().simulate('click');

expect(onEnterFolderSpy.withArgs(clickedFolder, newFoldersPath).calledOnce).toBe(true);
});
Expand All @@ -325,10 +340,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
const clickedFolder = items[clickedFolderIndex];
const newFoldersPath = initialFoldersPath.concat([clickedFolder]);

wrapper
.find('.item-list-name')
.first()
.simulate('doubleClick');
wrapper.find('.item-list-name').first().simulate('doubleClick');

expect(onEnterFolderSpy.withArgs(clickedFolder, newFoldersPath).calledOnce).toBe(true);
});
Expand All @@ -348,10 +360,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
const clickedFolder = items[clickedFolderIndex];
const newFoldersPath = initialFoldersPath.concat([clickedFolder]);

wrapper
.find('.item-list-name')
.first()
.simulate('click');
wrapper.find('.item-list-name').first().simulate('click');

expect(onEnterFolderSpy.withArgs(clickedFolder, newFoldersPath).calledOnce).toBe(false);
});
Expand All @@ -373,10 +382,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
];
const wrapper = renderComponent({ items, onSelectItem: onSelectItemSpy }, true);

wrapper
.find('.table-row')
.at(clickedItemIndex)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex).simulate('click');

expect(onSelectItemSpy.withArgs(items[clickedItemIndex], clickedItemIndex).calledOnce).toBe(true);
});
Expand All @@ -391,15 +397,9 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
];
const wrapper = renderComponent({ items, onSelectItem: onSelectItemSpy }, true);

wrapper
.find('.table-row')
.at(clickedItemIndex)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex).simulate('click');

wrapper
.find('.table-row')
.at(clickedItemIndex2)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex2).simulate('click');

expect(onSelectItemSpy.withArgs(items[clickedItemIndex], clickedItemIndex).calledOnce).toBe(true);

Expand All @@ -426,15 +426,9 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
true,
);

wrapper
.find('.table-row')
.at(clickedItemIndex)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex).simulate('click');

wrapper
.find('.table-row')
.at(clickedItemIndex2)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex2).simulate('click');

expect(onSelectItemSpy.withArgs(items[clickedItemIndex], clickedItemIndex).calledOnce).toBe(true);

Expand Down Expand Up @@ -483,20 +477,11 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
true,
);

wrapper
.find('.table-row')
.at(clickedItemIndex)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex).simulate('click');

wrapper
.find('.table-row')
.at(clickedItemIndex2)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex2).simulate('click');

wrapper
.find('.table-row')
.at(clickedItemIndex3)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex3).simulate('click');

expect(Object.keys(wrapper.state('selectedItems')).length).toBe(3);
expect(wrapper.state('isSelectAllChecked')).toBe(true);
Expand All @@ -509,7 +494,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
{ id: '1', name: 'item1' },
{ id: '2', name: 'item2' },
];
const selectedItems = { '1': items[clickedItemIndex], '2': items[clickedItemIndex2] };
const selectedItems = { 1: items[clickedItemIndex], 2: items[clickedItemIndex2] };
const wrapper = renderComponent(
{
items,
Expand All @@ -521,10 +506,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {
);
wrapper.setState({ selectedItems, isSelectAllChecked: true });

wrapper
.find('.table-row')
.at(clickedItemIndex)
.simulate('click');
wrapper.find('.table-row').at(clickedItemIndex).simulate('click');

expect(Object.keys(wrapper.state('selectedItems')).length).toBe(1);
expect(wrapper.state('isSelectAllChecked')).toBe(false);
Expand All @@ -540,7 +522,7 @@ describe('features/content-explorer/content-explorer/ContentExplorer', () => {

test('should call onChooseItems with the clicked file when double clicking a file', () => {
const items = [{ id: '1', name: 'item1', type: 'file' }];
const selectedItems = { '1': items[0] };
const selectedItems = { 1: items[0] };
const wrapper = renderComponent({ items, onChooseItems: onChooseItemsSpy }, true);

// Need to make the item selected first because simulating a double click doesn't actually click anything
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

import { render } from '../../../../test-utils/testing-library';

import ContentExplorerInfoNotice from '../ContentExplorerInfoNotice';

describe('features/content-explorer/content-explorer/ContentExplorerInfoNotice', () => {
const renderComponent = (infoNoticeText: string) =>
render(<ContentExplorerInfoNotice infoNoticeText={infoNoticeText} />);

test('should render correctly', () => {
const infoNoticeText = 'This is an info notice';
const { getByText } = renderComponent(infoNoticeText);
expect(getByText(infoNoticeText)).toBeInTheDocument();
});
});
5 changes: 5 additions & 0 deletions src/features/content-explorer/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ const messages = defineMessages({
description: 'Label text shown next to the Include Subfolders toggle',
id: 'boxui.contentExplorer.includeSubfolders',
},
infoNoticeIconAriaLabel: {
defaultMessage: 'Info icon',
description: 'Aria label for the info icon',
id: 'boxui.contentExplorer.infoNoticeIconAriaLabel',
},
});

export default messages;

0 comments on commit 20d4c3f

Please sign in to comment.