diff --git a/src/i18n/en-US.properties b/src/i18n/en-US.properties index edc9f4699a..69f66c9897 100644 --- a/src/i18n/en-US.properties +++ b/src/i18n/en-US.properties @@ -86,6 +86,8 @@ filename=Filename last_modified_date=Last modified date # Label for size column name size=Size +# Shown as the title in the breadcrumbs while searching. +search_results=Search Results # Media Preview # Label for autoplay in media player diff --git a/src/lib/viewers/archive/ArchiveExplorer.js b/src/lib/viewers/archive/ArchiveExplorer.js index c265bd5a98..f80065b5ef 100644 --- a/src/lib/viewers/archive/ArchiveExplorer.js +++ b/src/lib/viewers/archive/ArchiveExplorer.js @@ -9,7 +9,9 @@ import { } from 'box-ui-elements/es/features/virtualized-table-renderers'; import VirtualizedTable from 'box-ui-elements/es/features/virtualized-table'; import { Column } from 'react-virtualized/dist/es/Table/index'; -import { TABLE_COLUMNS } from './constants'; +import Breadcrumbs from './Breadcrumbs'; +import { TABLE_COLUMNS, VIEWS } from './constants'; +import './ArchiveExplorer.scss'; const { KEY_NAME, KEY_MODIFIED_AT, KEY_SIZE } = TABLE_COLUMNS; @@ -57,6 +59,7 @@ class ArchiveExplorer extends React.Component { this.state = { fullPath: props.itemCollection.find(info => !info.parent).absolute_path, + view: VIEWS.VIEW_FOLDER, }; } @@ -115,6 +118,14 @@ class ArchiveExplorer extends React.Component { }); }; + /** + * Handle click event, update fullPath state + * + * @param {string} fullPath - target folder path + * @return {void} + */ + handleClickFullPath = fullPath => this.setState({ fullPath }); + /** * render data * @@ -122,46 +133,49 @@ class ArchiveExplorer extends React.Component { */ render() { const { itemCollection } = this.props; - const { fullPath } = this.state; + const { fullPath, view } = this.state; const itemList = this.getItemList(itemCollection, fullPath); return ( - - {intl => [ - , - , - , - ]} - +
+ + + {intl => [ + , + , + , + ]} + +
); } diff --git a/src/lib/viewers/archive/ArchiveExplorer.scss b/src/lib/viewers/archive/ArchiveExplorer.scss new file mode 100644 index 0000000000..efd3401c96 --- /dev/null +++ b/src/lib/viewers/archive/ArchiveExplorer.scss @@ -0,0 +1,6 @@ +.bp-archive-explorer { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +} diff --git a/src/lib/viewers/archive/Breadcrumbs.js b/src/lib/viewers/archive/Breadcrumbs.js new file mode 100644 index 0000000000..ec7e03921a --- /dev/null +++ b/src/lib/viewers/archive/Breadcrumbs.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Breadcrumb from 'box-ui-elements/es/components/breadcrumb'; +import PlainButton from 'box-ui-elements/es/components/plain-button/PlainButton'; +import { VIEWS } from './constants'; +import './Breadcrumbs.scss'; + +class Breadcrumbs extends React.PureComponent { + static propTypes = { + fullPath: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + view: PropTypes.string.isRequired, + }; + + /** + * Split full path string to path items + * + * @param {string} fullPath - Full path for current folder + * @return {Array} path items including name and path string + */ + getPathItems = fullPath => { + const { view } = this.props; + + if (view === VIEWS.VIEW_SEARCH) { + return [ + { + name: __('search_results'), + }, + ]; + } + + const pathNames = fullPath.split('/').slice(0, -1); + + return pathNames.map((name, index) => ({ + name, + path: `${pathNames.slice(0, index + 1).join('/')}/`, + })); + }; + + /** + * render breadcrumbs + * + * @return {jsx} Breadcrumbs + */ + render() { + const { fullPath, onClick, view } = this.props; + const pathItems = this.getPathItems(fullPath); + + return ( +
+ + {pathItems.map(pathItem => + view === VIEWS.VIEW_SEARCH ? ( + {pathItem.name} + ) : ( + onClick(pathItem.path)} type="button"> + {pathItem.name} + + ), + )} + +
+ ); + } +} + +export default Breadcrumbs; diff --git a/src/lib/viewers/archive/Breadcrumbs.scss b/src/lib/viewers/archive/Breadcrumbs.scss new file mode 100644 index 0000000000..e173481391 --- /dev/null +++ b/src/lib/viewers/archive/Breadcrumbs.scss @@ -0,0 +1,11 @@ +@import '~box-ui-elements/es/styles/variables'; + +.bp-header-breadcrumbs { + display: flex; + flex: 0 0 50px; + align-items: center; + justify-content: space-between; + padding: 0 20px 0 25px; + border-bottom: 1px solid $bdl-gray-10; + box-shadow: 0 4px 6px -2px $transparent-black; +} diff --git a/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js b/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js index 9877d3c3fb..347d46af6e 100644 --- a/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js +++ b/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js @@ -68,9 +68,11 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { }); describe('render()', () => { - it('should render VirtualizedTable', () => { + it('should render correct components', () => { const component = shallow(); + expect(component.find('.bp-archive-explorer').length).to.equal(1); + expect(component.find('Breadcrumbs').length).to.equal(1); expect(component.find('Internationalize').length).to.equal(1); expect(component.find('InjectIntl(VirtualizedTable)').length).to.equal(1); }); @@ -86,6 +88,16 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { }); }); + describe('handleClickFullPath()', () => { + it('should set state when handleClickFullPath() is called', () => { + const component = shallow(); + + component.instance().handleClickFullPath('test/subfolder/'); + + expect(component.state().fullPath).to.equal('test/subfolder/'); + }); + }); + describe('getRowData()', () => { it('should return correct row data', () => { const component = shallow(); diff --git a/src/lib/viewers/archive/__tests__/Breadcrumbs-test-react.js b/src/lib/viewers/archive/__tests__/Breadcrumbs-test-react.js new file mode 100644 index 0000000000..45b489bf2f --- /dev/null +++ b/src/lib/viewers/archive/__tests__/Breadcrumbs-test-react.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; +import Breadcrumbs from '../Breadcrumbs'; +import { VIEWS } from '../constants'; + +const sandbox = sinon.sandbox.create(); +let fullPath; +let onClick; +let view; + +describe('lib/viewers/archive/Breadcrumbs', () => { + beforeEach(() => { + fullPath = 'test/subfolder/'; + onClick = sandbox.stub(); + view = VIEWS.VIEW_FOLDER; + }); + + afterEach(() => { + sandbox.verifyAndRestore(); + }); + + describe('render()', () => { + it('should render correct components', () => { + const component = shallow(); + + expect(component.find('.bp-header-breadcrumbs').length).to.equal(1); + expect(component.find('InjectIntl(Breadcrumb)').length).to.equal(1); + expect(component.find('PlainButton').length).to.equal(2); + }); + }); + + describe('getPathItems()', () => { + it('should return correct path items', () => { + const component = shallow(); + + const pathItems = component.instance().getPathItems(fullPath); + + expect(pathItems).to.eql([ + { + name: 'test', + path: 'test/', + }, + { + name: 'subfolder', + path: 'test/subfolder/', + }, + ]); + }); + + it('should return search results if view is search', () => { + const component = shallow(); + const pathItems = component.instance().getPathItems(fullPath); + + expect(pathItems).to.eql([ + { + name: __('search_results'), + }, + ]); + }); + }); +}); diff --git a/src/lib/viewers/archive/constants.js b/src/lib/viewers/archive/constants.js index c5b2c697f4..2ad6b50d9a 100644 --- a/src/lib/viewers/archive/constants.js +++ b/src/lib/viewers/archive/constants.js @@ -4,5 +4,9 @@ const TABLE_COLUMNS = { KEY_SIZE: 'key_size', }; -// eslint-disable-next-line import/prefer-default-export -export { TABLE_COLUMNS }; +const VIEWS = { + VIEW_FOLDER: 'folder', + VIEW_SEARCH: 'search', +}; + +export { TABLE_COLUMNS, VIEWS };