From 7b6af12125348cdf4491334699acde95c36fe3f6 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 11 Jan 2021 14:50:15 +0100 Subject: [PATCH] feat: responsive control bar and title bar [DHIS2-10138] (#1400) Partially implements DHIS2-10138 This PR addresses responsive changes in the view control bar and view title bar. Includes * spacing around chips, dashboard search field, against edge, chip scroll bars * remove action buttons (Edit, Share, Add filter) * remove "+" button (new button) * Width <=480 and collapsed control bar Implementation notes: * decoupled the View control bar from Edit control bar, since Edit mode doesn't need all the dnd functionality * For js part of the implementation, a new react hook was added that listens to window resizes. The components that need to make responsive changes can then use that hook. * The ChevronUp and ChevronDown icons are currently copied into this repo, but when we upgrade to ui6, then we'll switch to using the icons from there. * ControlBar was simplified since it is no longer used for the Edit bar (no editMode needed) * Where it made sense, I switched from inline css to css classes * The "Show more/less" button on the control bar is now an icon instead of text. Accessibility changes were made to adapt to this, like using a element and adding aria-label. Also added a tooltip, suggested by Joe. --- i18n/en.pot | 17 +- src/AppWrapper.js | 7 +- src/components/ControlBar/ControlBar.js | 30 +- src/components/ControlBar/DashboardsBar.js | 131 +++-- src/components/ControlBar/EditBar.js | 103 ++-- src/components/ControlBar/Filter.js | 20 +- src/components/ControlBar/ShowMoreButton.js | 28 +- .../ControlBar/__tests__/ControlBar.spec.js | 20 +- .../__tests__/DashboardsBar.spec.js | 101 ++-- .../ControlBar/__tests__/Filter.spec.js | 112 ++-- .../__tests__/ShowMoreButton.spec.js | 67 +-- .../__snapshots__/DashboardsBar.spec.js.snap | 161 ++++-- .../__snapshots__/EditBar.spec.js.snap | 483 +++++++----------- .../__snapshots__/Filter.spec.js.snap | 96 +++- .../__snapshots__/ShowMoreButton.spec.js.snap | 62 ++- src/components/ControlBar/assets/icons.js | 29 ++ .../ControlBar/styles/ControlBar.module.css | 8 +- .../styles/DashboardItemChip.module.css | 7 + .../styles/DashboardsBar.module.css | 68 ++- .../ControlBar/styles/EditBar.module.css | 16 + .../ControlBar/styles/Filter.module.css | 24 +- .../styles/ShowMoreButton.module.css | 14 +- src/components/Dashboard/Dashboard.js | 3 - .../Dashboard/DashboardVerticalOffset.js | 28 - src/components/Dashboard/ViewDashboard.js | 2 - .../Dashboard/__tests__/Dashboard.spec.js | 1 - .../Dashboard/__tests__/EditDashboard.spec.js | 1 - .../Dashboard/__tests__/ViewDashboard.spec.js | 1 - .../__snapshots__/Dashboard.spec.js.snap | 2 - .../__snapshots__/ViewDashboard.spec.js.snap | 1 - src/components/ItemFilter/FilterSelector.js | 4 +- .../styles/FilterSelector.module.css | 5 + src/components/TitleBar/ViewTitleBar.js | 9 +- .../TitleBar/styles/ViewTitleBar.module.css | 7 + src/components/WindowDimensionsProvider.js | 40 ++ src/modules/isSmallScreen.js | 5 + 36 files changed, 966 insertions(+), 747 deletions(-) create mode 100644 src/components/ControlBar/assets/icons.js create mode 100644 src/components/ControlBar/styles/EditBar.module.css delete mode 100644 src/components/Dashboard/DashboardVerticalOffset.js create mode 100644 src/components/ItemFilter/styles/FilterSelector.module.css create mode 100644 src/components/WindowDimensionsProvider.js create mode 100644 src/modules/isSmallScreen.js diff --git a/i18n/en.pot b/i18n/en.pot index 0f32be4f6..446656e49 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-12-10T12:16:21.390Z\n" -"PO-Revision-Date: 2020-12-10T12:16:21.391Z\n" +"POT-Creation-Date: 2021-01-05T09:32:21.165Z\n" +"PO-Revision-Date: 2021-01-05T09:32:21.165Z\n" msgid "Untitled dashboard" msgstr "" @@ -29,13 +29,13 @@ msgid "" "this dashboard?" msgstr "" -msgid "Exit Print preview" +msgid "Save changes" msgstr "" -msgid "Print preview" +msgid "Exit Print preview" msgstr "" -msgid "Save changes" +msgid "Print preview" msgstr "" msgid "Translate" @@ -50,10 +50,10 @@ msgstr "" msgid "Search for a dashboard" msgstr "" -msgid "Show more" +msgid "Show fewer dashboards" msgstr "" -msgid "Show less" +msgid "Show more dashboards" msgstr "" msgid "No dashboards found. Use the + button to create a new dashboard." @@ -174,6 +174,9 @@ msgstr "" msgid "Show fewer" msgstr "" +msgid "Show more" +msgstr "" + msgid "Insert" msgstr "" diff --git a/src/AppWrapper.js b/src/AppWrapper.js index 060535b3d..b64ba0929 100644 --- a/src/AppWrapper.js +++ b/src/AppWrapper.js @@ -5,6 +5,7 @@ import { Provider as ReduxProvider } from 'react-redux' import { D2Shim } from '@dhis2/app-runtime-adapter-d2' import { useDataEngine } from '@dhis2/app-runtime' +import WindowDimensionsProvider from './components/WindowDimensionsProvider' import App from './components/App' import configureStore from './configureStore' @@ -46,7 +47,11 @@ const AppWrapper = () => { // TODO: Handle errors in d2 initialization return null } - return + return ( + + + + ) }} diff --git a/src/components/ControlBar/ControlBar.js b/src/components/ControlBar/ControlBar.js index 49e14a301..1a3dd8978 100644 --- a/src/components/ControlBar/ControlBar.js +++ b/src/components/ControlBar/ControlBar.js @@ -1,7 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' -import { colors } from '@dhis2/ui' - +import cx from 'classnames' import classes from './styles/ControlBar.module.css' export const DRAG_HANDLE_HEIGHT = 7 @@ -48,34 +47,26 @@ class ControlBar extends React.Component { } } - renderDragHandle() { - return typeof this.props.onChangeHeight === 'function' ? ( + renderDragHandle = () => + typeof this.props.onChangeHeight === 'function' && (
- ) : null - } + ) render() { const height = Math.max(this.props.height, 0) + DRAG_HANDLE_HEIGHT - const rootStyle = Object.assign( - { - height, - backgroundColor: this.props.editMode - ? colors.yellow050 - : colors.white, - paddingBottom: DRAG_HANDLE_HEIGHT, - }, - // Disable animations while dragging - this.state.dragging ? { transition: 'none' } : {} + const rootClass = cx( + classes.root, + this.state.dragging && classes.dragging ) return ( -
+
{this.props.children}
{this.renderDragHandle()}
@@ -89,11 +80,6 @@ ControlBar.propTypes = { */ children: PropTypes.node.isRequired, - /** - * If true, the background color of the control bar changes to indicate that edit mode is active. - */ - editMode: PropTypes.bool.isRequired, - /** * Callback function that is called when the control bar is resized. * The callback receives one argument: The new height in pixels. diff --git a/src/components/ControlBar/DashboardsBar.js b/src/components/ControlBar/DashboardsBar.js index 2cc7a13af..d038f9b31 100644 --- a/src/components/ControlBar/DashboardsBar.js +++ b/src/components/ControlBar/DashboardsBar.js @@ -1,12 +1,11 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, createRef } from 'react' import { connect } from 'react-redux' import { Link, withRouter } from 'react-router-dom' import cx from 'classnames' - import arraySort from 'd2-utilizr/lib/arraySort' import PropTypes from 'prop-types' -import ControlBar from './ControlBar' +import ControlBar, { DRAG_HANDLE_HEIGHT } from './ControlBar' import Chip from './DashboardItemChip' import AddCircleIcon from '../../icons/AddCircle' import Filter from './Filter' @@ -18,6 +17,7 @@ import { getControlBarHeight, getNumRowsFromHeight, } from './controlBarDimensions' +import { useWindowDimensions } from '../WindowDimensionsProvider' import { sGetDashboardsFilter } from '../../reducers/dashboardsFilter' import { sGetControlBarUserRows } from '../../reducers/controlBar' import { sGetAllDashboards } from '../../reducers/dashboards' @@ -25,11 +25,13 @@ import { sGetSelectedId } from '../../reducers/selected' import { acSetControlBarUserRows } from '../../actions/controlBar' import { apiPostControlBarRows } from '../../api/controlBar' +import isSmallScreen from '../../modules/isSmallScreen' + import classes from './styles/DashboardsBar.module.css' export const MAX_ROW_COUNT = 10 -export const DashboardsBar = ({ +const DashboardsBar = ({ userRows, onChangeHeight, history, @@ -38,6 +40,8 @@ export const DashboardsBar = ({ filterText, }) => { const [rows, setRows] = useState(userRows) + const { width } = useWindowDimensions() + const ref = createRef() useEffect(() => { setRows(userRows) @@ -58,12 +62,20 @@ export const DashboardsBar = ({ const onEndDrag = () => apiPostControlBarRows(rows) + const scrollToTop = () => { + if (isMaxHeight()) { + ref.current.scroll(0, 0) + } + } + const toggleMaxHeight = () => { const newRows = isMaxHeight() ? userRows : MAX_ROW_COUNT + scrollToTop() setRows(newRows) } const cancelMaxHeight = () => { + scrollToTop() setRows(userRows) } @@ -89,50 +101,85 @@ export const DashboardsBar = ({ ] } - const overflowYClass = isMaxHeight() - ? classes.overflowYAuto - : classes.overflowYHidden + const containerClass = cx( + classes.container, + isMaxHeight() ? classes.expanded : classes.collapsed + ) + + const viewableRows = + isSmallScreen(width) && !isMaxHeight() ? MIN_ROW_COUNT : rows + + const rowHeightProp = { + height: getRowsHeight(viewableRows) + FIRST_ROW_PADDING_HEIGHT, + } + + const getDashboardChips = () => { + const chips = getFilteredDashboards().map(dashboard => ( + + )) + if (isSmallScreen(width)) { + const chipContainerClasses = cx( + classes.chipContainer, + isMaxHeight() ? classes.expanded : classes.collapsed + ) + return ( +
+ {chips} +
+ ) + } else { + return chips + } + } return ( - + <> + +
+
+ + + + +
+ {getDashboardChips()} +
+ +
-
- - - - -
- {getFilteredDashboards().map(dashboard => ( - - ))} -
- -
+ ) } diff --git a/src/components/ControlBar/EditBar.js b/src/components/ControlBar/EditBar.js index 41cde36f4..5da0e5db2 100644 --- a/src/components/ControlBar/EditBar.js +++ b/src/components/ControlBar/EditBar.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Redirect } from 'react-router-dom' import i18n from '@dhis2/d2-i18n' -import ControlBar from './ControlBar' import TranslationDialog from '@dhis2/d2-ui-translation-dialog' import { Button, ButtonStrip } from '@dhis2/ui' @@ -11,6 +10,8 @@ import ConfirmDeleteDialog from './ConfirmDeleteDialog' import { tSaveDashboard, acClearEditDashboard, + acSetPrintPreviewView, + acClearPrintPreviewView, } from '../../actions/editDashboard' import { acClearPrintDashboard } from '../../actions/printDashboard' import { @@ -22,25 +23,9 @@ import { sGetIsNewDashboard, sGetIsPrintPreviewView, } from '../../reducers/editDashboard' -import { - acSetPrintPreviewView, - acClearPrintPreviewView, -} from '../../actions/editDashboard' -import { - CONTROL_BAR_ROW_HEIGHT, - MIN_ROW_COUNT, - getControlBarHeight, -} from './controlBarDimensions' import { apiFetchDashboard } from '../../api/dashboards' -import classes from './styles/DashboardsBar.module.css' - -const buttonBarStyle = { - height: CONTROL_BAR_ROW_HEIGHT, - paddingTop: '15px', - marginLeft: '15px', - marginRight: '15px', -} +import classes from './styles/EditBar.module.css' export class EditBar extends Component { state = { @@ -161,41 +146,36 @@ export class EditBar extends Component { /> ) : null - renderActionButtons = () => { - const printPreviewText = this.props.isPrintPreviewView - ? i18n.t('Exit Print preview') - : i18n.t('Print preview') - return ( -
- - - - - {this.props.dashboardId ? ( - - ) : null} - {this.props.dashboardId && this.props.deleteAccess ? ( - - ) : null} - -
- ) - } + renderActionButtons = () => ( + + + + + {this.props.dashboardId && ( + + )} + {this.props.dashboardId && this.props.deleteAccess && ( + + )} + + ) render() { if (this.state.redirectUrl) { @@ -203,7 +183,6 @@ export class EditBar extends Component { } const { updateAccess } = this.props - const controlBarHeight = getControlBarHeight(MIN_ROW_COUNT) const discardBtnText = updateAccess ? i18n.t('Exit without saving') @@ -211,18 +190,14 @@ export class EditBar extends Component { return ( <> - -
+
+
{updateAccess ? this.renderActionButtons() : null} - -
- -
+
- -
+
{this.translationDialog()} {this.confirmDeleteDialog()} diff --git a/src/components/ControlBar/Filter.js b/src/components/ControlBar/Filter.js index a179b61ed..b8320b45f 100644 --- a/src/components/ControlBar/Filter.js +++ b/src/components/ControlBar/Filter.js @@ -6,24 +6,30 @@ import cx from 'classnames' import SearchIcon from '../../icons/Search' import ClearButton from './ClearButton' +import { useWindowDimensions } from '../WindowDimensionsProvider' import { acSetDashboardsFilter, acClearDashboardsFilter, } from '../../actions/dashboardsFilter' import { sGetDashboardsFilter } from '../../reducers/dashboardsFilter' +import isSmallScreen from '../../modules/isSmallScreen' import classes from './styles/Filter.module.css' export const KEYCODE_ENTER = 13 export const KEYCODE_ESCAPE = 27 -export const Filter = ({ +export const FilterUnconnected = ({ clearDashboardsFilter, filterText, + isMaxHeight, setDashboardsFilter, onKeypressEnter, + onToggleMaxHeight, }) => { const [focusedClassName, setFocusedClassName] = useState('') + const { width } = useWindowDimensions() + const setFilterValue = event => { event.preventDefault() setDashboardsFilter(event.target.value) @@ -53,7 +59,11 @@ export const Filter = ({ setFocusedClassName('') } - return ( + return isSmallScreen(width) && !isMaxHeight ? ( + + ) : (
({ @@ -90,4 +102,4 @@ const mapDispatchToProps = { clearDashboardsFilter: acClearDashboardsFilter, } -export default connect(mapStateToProps, mapDispatchToProps)(Filter) +export default connect(mapStateToProps, mapDispatchToProps)(FilterUnconnected) diff --git a/src/components/ControlBar/ShowMoreButton.js b/src/components/ControlBar/ShowMoreButton.js index ee0bea915..672cddf4a 100644 --- a/src/components/ControlBar/ShowMoreButton.js +++ b/src/components/ControlBar/ShowMoreButton.js @@ -1,24 +1,34 @@ import React from 'react' import PropTypes from 'prop-types' import i18n from '@dhis2/d2-i18n' +import { Tooltip } from '@dhis2/ui' +import { ChevronDown, ChevronUp } from './assets/icons' import classes from './styles/ShowMoreButton.module.css' export const SHOWMORE_BAR_HEIGHT = 16 -export const ShowMoreButton = ({ onClick, isMaxHeight, disabled }) => { +const ShowMoreButton = ({ onClick, isMaxHeight, disabled }) => { + const buttonLabel = isMaxHeight + ? i18n.t('Show fewer dashboards') + : i18n.t('Show more dashboards') return (
{disabled ? ( -
{i18n.t('Show more')}
- ) : ( -
- {isMaxHeight ? i18n.t('Show less') : i18n.t('Show more')} +
+
+ ) : ( + + + )}
) diff --git a/src/components/ControlBar/__tests__/ControlBar.spec.js b/src/components/ControlBar/__tests__/ControlBar.spec.js index 4d00310f2..5b338f19b 100644 --- a/src/components/ControlBar/__tests__/ControlBar.spec.js +++ b/src/components/ControlBar/__tests__/ControlBar.spec.js @@ -3,12 +3,6 @@ import { shallow } from 'enzyme' import ControlBar from '../ControlBar' -jest.mock('@dhis2/ui', () => { - return { - colors: { white: 'white', yellow050: 'yellow' }, - } -}) - describe('ControlBar', () => { let props const children = ( @@ -50,14 +44,6 @@ describe('ControlBar', () => { expect(renderControlBar().children().length).toBe(1) }) - it('should change the background color in edit mode', () => { - props.editMode = true - - expect(renderControlBar().props().style.backgroundColor).not.toBe( - 'white' - ) - }) - it('should call the onChangeHeight callback when the drag handle is dragged', () => { // Replace the async DOM function with a sync function that immediately executes the callback window.requestAnimationFrame = requestAnimationFrame @@ -105,13 +91,13 @@ describe('ControlBar', () => { const controlBar = renderControlBar() const dragFlap = controlBar.childAt(1) - expect(controlBar.props().style.transition).not.toBe('none') + expect(controlBar.props().className).not.toMatch(/dragging/) dragFlap.simulate('mousedown') - expect(controlBar.props().style.transition).toBe('none') + expect(controlBar.props().className).toMatch(/dragging/) window.dispatchEvent(new MouseEvent('mouseup')) controlBar.update() - expect(controlBar.props().style.transition).not.toBe('none') + expect(controlBar.props().className).not.toMatch(/dragging/) }) it('should start listening for mousemove and mouseup events when the drag flap is clicked', () => { diff --git a/src/components/ControlBar/__tests__/DashboardsBar.spec.js b/src/components/ControlBar/__tests__/DashboardsBar.spec.js index 1aeb72310..4b4efe52b 100644 --- a/src/components/ControlBar/__tests__/DashboardsBar.spec.js +++ b/src/components/ControlBar/__tests__/DashboardsBar.spec.js @@ -4,6 +4,7 @@ import { fireEvent } from '@testing-library/dom' import { Provider } from 'react-redux' import configureMockStore from 'redux-mock-store' import { Router } from 'react-router-dom' +import WindowDimensionsProvider from '../../WindowDimensionsProvider' import { createMemoryHistory } from 'history' import DashboardsBar, { MAX_ROW_COUNT } from '../DashboardsBar' import { MIN_ROW_COUNT } from '../controlBarDimensions' @@ -36,11 +37,13 @@ test('renders a DashboardsBar with minimum height', () => { selected: { id: 'rainbow123' }, } const { container } = render( - - - - - + + + + + + + ) expect(container).toMatchSnapshot() }) @@ -53,11 +56,13 @@ test('renders a DashboardsBar with maximum height', () => { selected: { id: 'rainbow123' }, } const { container } = render( - - - - - + + + + + + + ) expect(container).toMatchSnapshot() }) @@ -71,11 +76,13 @@ test('renders a DashboardsBar with selected item', () => { } const { container } = render( - - - - - + + + + + + + ) expect(container).toMatchSnapshot() }) @@ -89,11 +96,13 @@ test('renders a DashboardsBar with no items', () => { } const { container } = render( - - - - - + + + + + + + ) expect(container).toMatchSnapshot() }) @@ -105,15 +114,17 @@ test('clicking "Show more" maximizes dashboards bar height', () => { controlBar: { userRows: MIN_ROW_COUNT }, selected: { id: 'fluttershy123' }, } - const { getByText, asFragment } = render( - - - - - + const { getByLabelText, asFragment } = render( + + + + + + + ) - fireEvent.click(getByText('Show more')) + fireEvent.click(getByLabelText('Show more dashboards')) expect(asFragment()).toMatchSnapshot() }) @@ -125,11 +136,13 @@ test('triggers onChangeHeight when controlbar height is changed', () => { selected: { id: 'fluttershy123' }, }) const { getByTestId } = render( - - - - - + + + + + + + ) const spy = jest.spyOn(api, 'apiPostControlBarRows') @@ -157,11 +170,13 @@ test('does not trigger onChangeHeight when controlbar height is changed to simil selected: { id: 'fluttershy123' }, }) const { getByTestId } = render( - - - - - + + + + + + + ) const spy = jest.spyOn(api, 'apiPostControlBarRows') @@ -186,11 +201,13 @@ test('calls the api to post user rows when drag ends', () => { selected: { id: 'rainbow123' }, } const { getByTestId } = render( - - - - - + + + + + + + ) const spy = jest.spyOn(api, 'apiPostControlBarRows') diff --git a/src/components/ControlBar/__tests__/Filter.spec.js b/src/components/ControlBar/__tests__/Filter.spec.js index 59fedd040..1c56d34fb 100644 --- a/src/components/ControlBar/__tests__/Filter.spec.js +++ b/src/components/ControlBar/__tests__/Filter.spec.js @@ -1,52 +1,72 @@ import React from 'react' -import { shallow } from 'enzyme' -import toJson from 'enzyme-to-json' -import { Filter, KEYCODE_ENTER, KEYCODE_ESCAPE } from '../Filter' +import { mount } from 'enzyme' +import { render } from '@testing-library/react' +import { Provider } from 'react-redux' +import configureMockStore from 'redux-mock-store' +import Filter, { + FilterUnconnected, + KEYCODE_ENTER, + KEYCODE_ESCAPE, +} from '../Filter' +import WindowDimensionsProvider from '../../WindowDimensionsProvider' -describe('Filter', () => { - let props - let shallowFilter - const filter = () => { - if (!shallowFilter) { - shallowFilter = shallow() - } - return shallowFilter - } - - beforeEach(() => { - props = { - filterText: '', - setDashboardsFilter: jest.fn(), - clearDashboardsFilter: jest.fn(), - onKeypressEnter: jest.fn(), - classes: {}, - } - shallowFilter = undefined - }) +const mockStore = configureMockStore() +describe('Filter', () => { it('matches the snapshot when filterText property is empty', () => { - expect(toJson(filter())).toMatchSnapshot() + const store = { + dashboardsFilter: '', + } + const props = { classes: {} } + const { container } = render( + + + + + + ) + expect(container).toMatchSnapshot() }) - describe('when updated filterText property is provided', () => { + describe('when filterText property is provided', () => { it('renders an input with correct value property', () => { - const filterWrapper = filter() - filterWrapper.setProps({ filterText: 'rainbow' }) + const store = { + dashboardsFilter: 'rainbow', + } + + const props = { classes: {} } + const { container } = render( + + + + + + ) - expect(filterWrapper.find('input').props().value).toEqual('rainbow') - expect(props.setDashboardsFilter).not.toHaveBeenCalled() + expect(container).toMatchSnapshot() }) }) describe('when input value is changed', () => { it('triggers setDashboardsFilter property with correct value', () => { + const props = { + filterText: '', + setDashboardsFilter: jest.fn(), + clearDashboardsFilter: jest.fn(), + onKeypressEnter: jest.fn(), + classes: {}, + } const newValue = 'fluttershy' const e = { target: { value: newValue }, preventDefault: jest.fn(), } - const inputField = filter().find('input') - inputField.simulate('change', e) + const filter = mount( + + + + ) + filter.find('input').simulate('change', e) expect(e.preventDefault).toHaveBeenCalledTimes(1) expect(props.setDashboardsFilter).toHaveBeenCalledTimes(1) expect(props.setDashboardsFilter).toHaveBeenCalledWith(newValue) @@ -55,19 +75,37 @@ describe('Filter', () => { describe('when key is pressed', () => { it('triggers onKeypressEnter when key is ENTER', () => { - filter().find('input').simulate('keyUp', { keyCode: KEYCODE_ENTER }) + const props = { + filterText: '', + clearDashboardsFilter: jest.fn(), + onKeypressEnter: jest.fn(), + classes: {}, + } + const filter = mount( + + + + ) + filter.find('input').simulate('keyUp', { keyCode: KEYCODE_ENTER }) expect(props.onKeypressEnter).toHaveBeenCalledTimes(1) expect(props.clearDashboardsFilter).toHaveBeenCalledTimes(1) }) it('triggers setDashboardsFilter when key is ESCAPE', () => { - props.setDashboardsFilter = jest.fn() - props.onKeypressEnter = jest.fn() + const props = { + filterText: '', + clearDashboardsFilter: jest.fn(), + onKeypressEnter: jest.fn(), + classes: {}, + } + const filter = mount( + + + + ) - filter() - .find('input') - .simulate('keyUp', { keyCode: KEYCODE_ESCAPE }) + filter.find('input').simulate('keyUp', { keyCode: KEYCODE_ESCAPE }) expect(props.clearDashboardsFilter).toHaveBeenCalledTimes(1) expect(props.onKeypressEnter).not.toHaveBeenCalled() diff --git a/src/components/ControlBar/__tests__/ShowMoreButton.spec.js b/src/components/ControlBar/__tests__/ShowMoreButton.spec.js index 3906e7af9..c396e7fc1 100644 --- a/src/components/ControlBar/__tests__/ShowMoreButton.spec.js +++ b/src/components/ControlBar/__tests__/ShowMoreButton.spec.js @@ -1,22 +1,22 @@ import React from 'react' -import { shallow } from 'enzyme' -import toJson from 'enzyme-to-json' -import { ShowMoreButton } from '../ShowMoreButton' +import { render } from '@testing-library/react' +import { fireEvent } from '@testing-library/dom' +import ShowMoreButton from '../ShowMoreButton' describe('ShowMoreButton', () => { it('renders correctly when at maxHeight', () => { - const button = shallow( + const { container } = render( {}} isMaxHeight={true} classes={{ showMore: {} }} /> ) - expect(toJson(button)).toMatchSnapshot() + expect(container).toMatchSnapshot() }) it('renders correctly when not at maxHeight', () => { - const tree = shallow( + const { container } = render( {}} isMaxHeight={false} @@ -24,50 +24,19 @@ describe('ShowMoreButton', () => { /> ) - expect(toJson(tree)).toMatchSnapshot() + expect(container).toMatchSnapshot() }) - describe('actions', () => { - let props - let shallowShowMoreButton - const showMoreButton = () => { - if (!shallowShowMoreButton) { - shallowShowMoreButton = shallow() - } - return shallowShowMoreButton - } - - beforeEach(() => { - props = { - onClick: undefined, - isMaxHeight: undefined, - classes: { showMore: {} }, - } - shallowShowMoreButton = undefined - }) - - it('changes text when isMaxHeight is changed', () => { - props.isMaxHeight = true - const button1Text = showMoreButton().text() - - shallowShowMoreButton = undefined - props.isMaxHeight = false - const button2Text = showMoreButton().text() - - expect(button1Text.length).toBeGreaterThan(0) - expect(button2Text.length).toBeGreaterThan(0) - expect(button1Text).not.toEqual(button2Text) - }) - - describe('when onClick is defined', () => { - beforeEach(() => { - props.onClick = jest.fn() - }) - - it('triggers onClick when div clicked', () => { - showMoreButton().childAt(0).simulate('click') - expect(props.onClick).toHaveBeenCalled() - }) - }) + it('triggers onClick when button clicked', () => { + const onClick = jest.fn() + const { getByLabelText } = render( + + ) + fireEvent.click(getByLabelText('Show more dashboards')) + expect(onClick).toHaveBeenCalled() }) }) diff --git a/src/components/ControlBar/__tests__/__snapshots__/DashboardsBar.spec.js.snap b/src/components/ControlBar/__tests__/__snapshots__/DashboardsBar.spec.js.snap index e270ca79b..bfe4acfaa 100644 --- a/src/components/ControlBar/__tests__/__snapshots__/DashboardsBar.spec.js.snap +++ b/src/components/ControlBar/__tests__/__snapshots__/DashboardsBar.spec.js.snap @@ -4,17 +4,17 @@ exports[`clicking "Show more" maximizes dashboards bar height 1`] = `
+
`; @@ -139,17 +158,17 @@ exports[`renders a DashboardsBar with maximum height 1`] = `
+
`; @@ -274,17 +306,17 @@ exports[`renders a DashboardsBar with minimum height 1`] = `
+
`; @@ -410,17 +461,17 @@ exports[`renders a DashboardsBar with no items 1`] = `
+
`; @@ -492,17 +562,17 @@ exports[`renders a DashboardsBar with selected item 1`] = `
+
`; diff --git a/src/components/ControlBar/__tests__/__snapshots__/EditBar.spec.js.snap b/src/components/ControlBar/__tests__/__snapshots__/EditBar.spec.js.snap index 7b90b7671..a11425acc 100644 --- a/src/components/ControlBar/__tests__/__snapshots__/EditBar.spec.js.snap +++ b/src/components/ControlBar/__tests__/__snapshots__/EditBar.spec.js.snap @@ -2,145 +2,90 @@ exports[`EditBar renders the EditBar 1`] = ` -
-
- -
+ Go to dashboards +
-
-
+
`; exports[`EditBar when update access is false renders only the Discard button 1`] = ` -
-
- -
+ Go to dashboards +
-
-
+
`; exports[`EditBar when update access is true when dashboard id property provided renders Save, Translate and Discard buttons and the TranslationDialog but not ConfirmDeleteDialog 1`] = ` -
-
- - - - - -
-
+ Save changes + -
+ + +
-
-
+
-
-
- - - - - -
-
+ Save changes + + -
+ +
-
-
+
-
-
- - - - - -
-
+ Save changes + -
+ + +
-
-
+
-
-
- - - - - - -
-
+ Save changes + -
+ + + +
-
-
+
-
-
- - - - -
-
+ Save changes + -
+ +
-
-
+
`; diff --git a/src/components/ControlBar/__tests__/__snapshots__/Filter.spec.js.snap b/src/components/ControlBar/__tests__/__snapshots__/Filter.spec.js.snap index f06a5505a..3f0a03382 100644 --- a/src/components/ControlBar/__tests__/__snapshots__/Filter.spec.js.snap +++ b/src/components/ControlBar/__tests__/__snapshots__/Filter.spec.js.snap @@ -1,22 +1,84 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Filter matches the snapshot when filterText property is empty 1`] = ` -
- - +
+
+ + + + + + +
+
+`; + +exports[`Filter when filterText property is provided renders an input with correct value property 1`] = ` +
+
+ + + + + + +
`; diff --git a/src/components/ControlBar/__tests__/__snapshots__/ShowMoreButton.spec.js.snap b/src/components/ControlBar/__tests__/__snapshots__/ShowMoreButton.spec.js.snap index 4bdc17356..1994b2230 100644 --- a/src/components/ControlBar/__tests__/__snapshots__/ShowMoreButton.spec.js.snap +++ b/src/components/ControlBar/__tests__/__snapshots__/ShowMoreButton.spec.js.snap @@ -1,29 +1,63 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ShowMoreButton renders correctly when at maxHeight 1`] = ` -
+
- Show less + + +
`; exports[`ShowMoreButton renders correctly when not at maxHeight 1`] = ` -
+
- Show more + + +
`; diff --git a/src/components/ControlBar/assets/icons.js b/src/components/ControlBar/assets/icons.js new file mode 100644 index 000000000..b7b8ccf3d --- /dev/null +++ b/src/components/ControlBar/assets/icons.js @@ -0,0 +1,29 @@ +import React from 'react' + +export const ChevronDown = () => ( + + + +) + +export const ChevronUp = () => ( + + + +) diff --git a/src/components/ControlBar/styles/ControlBar.module.css b/src/components/ControlBar/styles/ControlBar.module.css index 59cd90238..e0e77162d 100644 --- a/src/components/ControlBar/styles/ControlBar.module.css +++ b/src/components/ControlBar/styles/ControlBar.module.css @@ -2,12 +2,17 @@ position: fixed; left: 0px; right: 0px; - background: white; + background-color: var(--colors-white); box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 6px 3px; transition: all ease-out 75ms; z-index: 10; overflow: hidden; box-sizing: border-box; + padding-bottom: 7px; +} + +.dragging { + transition: 'none'; } .content { @@ -21,7 +26,6 @@ bottom: 0px; left: 0px; right: 0px; - height: 10; cursor: ns-resize; transition: all ease-out 75ms; } diff --git a/src/components/ControlBar/styles/DashboardItemChip.module.css b/src/components/ControlBar/styles/DashboardItemChip.module.css index 8bfae7b39..1bcb39d88 100644 --- a/src/components/ControlBar/styles/DashboardItemChip.module.css +++ b/src/components/ControlBar/styles/DashboardItemChip.module.css @@ -8,9 +8,16 @@ margin-top: 2px; width: 20px; } + .selected { fill: var(--colors-white); } .unselected { fill: var(--colors-grey700); } + +@media only screen and (max-width: 480px) { + .link { + margin: 0 -2px; + } +} diff --git a/src/components/ControlBar/styles/DashboardsBar.module.css b/src/components/ControlBar/styles/DashboardsBar.module.css index 112457998..331a0e5e5 100644 --- a/src/components/ControlBar/styles/DashboardsBar.module.css +++ b/src/components/ControlBar/styles/DashboardsBar.module.css @@ -1,35 +1,65 @@ .container { padding: 10px 6px 0 6px; + overflow: hidden; } -.overflowYAuto { +.container.expanded { overflow-y: auto; } -.overflowYHidden { - overflow-y: hidden; -} - -.leftControls { - float: left; - height: 36px; - position: relative; - top: 2px; - margin-right: 4px; +.controls { + display: inline; } .newLink { display: inline-block; text-decoration: none; - margin-right: 10px; - position: relative; - top: 2px; + margin-right: var(--spacers-dp8); + margin-top: var(--spacers-dp4); } -.rightControls { - float: right; -} +@media only screen and (max-width: 480px) { + .controls { + margin-bottom: var(--spacers-dp8); + margin-top: var(--spacers-dp8); + } + .newLink { + display: none; + } + + .container { + padding: 4px 0px 4px 4px; + display: flex; + flex-wrap: wrap; + } + + .container.collapsed { + flex-wrap: nowrap; + } + + .container.expanded { + overflow-y: hidden; + } + + .container.expanded .controls { + margin-left: var(--spacers-dp4); + } + + .chipContainer { + margin-bottom: -4px; + } + + .chipContainer.expanded { + overflow-x: hidden; + overflow-y: auto; + padding-left: 2px; + padding-right: 6px; + } -.topMargin { - margin-top: 71px; + .chipContainer.collapsed { + overflow-x: auto; + overflow-y: hidden; + display: flex; + flex-wrap: nowrap; + } } diff --git a/src/components/ControlBar/styles/EditBar.module.css b/src/components/ControlBar/styles/EditBar.module.css new file mode 100644 index 000000000..d7527ed15 --- /dev/null +++ b/src/components/ControlBar/styles/EditBar.module.css @@ -0,0 +1,16 @@ +.editBar { + display: flex; + flex-direction: column; + justify-content: center; + height: 71px; + background-color: var(--colors-yellow050); + box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 6px 3px; + position: relative; + z-index: 10; +} + +.controls { + display: flex; + justify-content: space-between; + padding: 0 var(--spacers-dp8); +} diff --git a/src/components/ControlBar/styles/Filter.module.css b/src/components/ControlBar/styles/Filter.module.css index 8b10f56a4..8908bd968 100644 --- a/src/components/ControlBar/styles/Filter.module.css +++ b/src/components/ControlBar/styles/Filter.module.css @@ -1,18 +1,17 @@ .container { width: 200px; - height: 30px; - top: -4px; + height: 36px; position: relative; align-items: center; display: inline-flex; line-height: 1.1875em; + padding-top: var(--spacers-dp8); } .input { font-size: 14px; border: none; margin-left: var(--spacers-dp8); - padding: 6px 0 7px 0; } .input::placeholder { @@ -50,6 +49,21 @@ .searchIcon { fill: var(--colors-grey700); - width: 20px; - height: 20px; +} + +.searchButton { + border: none; + background-color: transparent; + padding: 0px 6px 0px 0px; +} + +.searchButton:hover { + cursor: pointer; +} + +@media only screen and (max-width: 480px) { + .container { + height: 24px; + padding-top: inherit; + } } diff --git a/src/components/ControlBar/styles/ShowMoreButton.module.css b/src/components/ControlBar/styles/ShowMoreButton.module.css index e401a2404..f87468788 100644 --- a/src/components/ControlBar/styles/ShowMoreButton.module.css +++ b/src/components/ControlBar/styles/ShowMoreButton.module.css @@ -3,19 +3,17 @@ } .showMore { - color: var(--colors-grey700); cursor: pointer; - font-size: 11px; - padding-top: 5px; + border: none; + background-color: transparent; + padding: 0px; + width: 100%; } -.showMore:hover { - text-decoration: underline; +.showMore:focus { + outline: none; } .disabled { - padding-top: 5px; - color: var(--colors-grey500); - font-size: 11px; cursor: not-allowed; } diff --git a/src/components/Dashboard/Dashboard.js b/src/components/Dashboard/Dashboard.js index 29516ad19..cee09299a 100644 --- a/src/components/Dashboard/Dashboard.js +++ b/src/components/Dashboard/Dashboard.js @@ -7,7 +7,6 @@ import { Layer, CenteredContent, CircularLoader } from '@dhis2/ui' import debounce from 'lodash/debounce' import DashboardsBar from '../ControlBar/DashboardsBar' -import DashboardVerticalOffset from './DashboardVerticalOffset' import NoContentMessage from '../../widgets/NoContentMessage' import ViewDashboard from './ViewDashboard' import EditDashboard from './EditDashboard' @@ -99,7 +98,6 @@ export const Dashboard = ({ return ( <> - - diff --git a/src/components/Dashboard/DashboardVerticalOffset.js b/src/components/Dashboard/DashboardVerticalOffset.js deleted file mode 100644 index f57701d4f..000000000 --- a/src/components/Dashboard/DashboardVerticalOffset.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import PropTypes from 'prop-types' - -import { - getControlBarHeight, - MIN_ROW_COUNT, -} from '../ControlBar/controlBarDimensions' -import { sGetControlBarUserRows } from '../../reducers/controlBar' -import { DRAG_HANDLE_HEIGHT } from '../ControlBar/ControlBar' - -const DashboardVerticalOffset = props => { - const rows = props.editMode ? MIN_ROW_COUNT : props.userRows - const marginTop = getControlBarHeight(rows) + DRAG_HANDLE_HEIGHT - - return
-} - -const mapStateToProps = state => ({ - userRows: sGetControlBarUserRows(state), -}) - -DashboardVerticalOffset.propTypes = { - editMode: PropTypes.bool, - userRows: PropTypes.number, -} - -export default connect(mapStateToProps)(DashboardVerticalOffset) diff --git a/src/components/Dashboard/ViewDashboard.js b/src/components/Dashboard/ViewDashboard.js index 23d4bccc1..2ffaae890 100644 --- a/src/components/Dashboard/ViewDashboard.js +++ b/src/components/Dashboard/ViewDashboard.js @@ -6,7 +6,6 @@ import TitleBar from '../TitleBar/TitleBar' import ItemGrid from '../ItemGrid/ItemGrid' import FilterBar from '../FilterBar/FilterBar' import DashboardsBar from '../ControlBar/DashboardsBar' -import DashboardVerticalOffset from './DashboardVerticalOffset' import { sGetIsEditing } from '../../reducers/editDashboard' import { sGetIsPrinting } from '../../reducers/printDashboard' import { sGetSelectedId } from '../../reducers/selected' @@ -40,7 +39,6 @@ export const ViewDashboard = props => { return ( <> -
diff --git a/src/components/Dashboard/__tests__/Dashboard.spec.js b/src/components/Dashboard/__tests__/Dashboard.spec.js index c5717a5e0..c679a5d86 100644 --- a/src/components/Dashboard/__tests__/Dashboard.spec.js +++ b/src/components/Dashboard/__tests__/Dashboard.spec.js @@ -7,7 +7,6 @@ import { NEW, VIEW, EDIT, PRINT, PRINT_LAYOUT } from '../dashboardModes' import { NON_EXISTING_DASHBOARD_ID } from '../../../reducers/selected' jest.mock('../../ControlBar/DashboardsBar', () => 'DashboardsBar') -jest.mock('../DashboardVerticalOffset', () => 'DashboardVerticalOffset') jest.mock('../../../widgets/NoContentMessage', () => 'NoContentMessage') jest.mock('../ViewDashboard', () => 'ViewDashboard') jest.mock('../EditDashboard', () => 'EditDashboard') diff --git a/src/components/Dashboard/__tests__/EditDashboard.spec.js b/src/components/Dashboard/__tests__/EditDashboard.spec.js index 1c0edd105..c0c262a1d 100644 --- a/src/components/Dashboard/__tests__/EditDashboard.spec.js +++ b/src/components/Dashboard/__tests__/EditDashboard.spec.js @@ -4,7 +4,6 @@ import toJson from 'enzyme-to-json' import { EditDashboard } from '../EditDashboard' -jest.mock('../DashboardVerticalOffset', () => 'DashboardVerticalOffset') jest.mock('../../ControlBar/EditBar', () => 'EditBar') jest.mock('../../TitleBar/TitleBar', () => 'TitleBar') jest.mock('../../ItemGrid/ItemGrid', () => 'ItemGrid') diff --git a/src/components/Dashboard/__tests__/ViewDashboard.spec.js b/src/components/Dashboard/__tests__/ViewDashboard.spec.js index 5fb9c3334..e5d3fd111 100644 --- a/src/components/Dashboard/__tests__/ViewDashboard.spec.js +++ b/src/components/Dashboard/__tests__/ViewDashboard.spec.js @@ -9,7 +9,6 @@ jest.mock('react', () => ({ })) jest.mock('../../ControlBar/DashboardsBar', () => 'DashboardsBar') -jest.mock('../DashboardVerticalOffset', () => 'DashboardVerticalOffset') jest.mock('../../TitleBar/TitleBar', () => 'TitleBar') jest.mock('../../FilterBar/FilterBar', () => 'FilterBar') jest.mock('../../ItemGrid/ItemGrid', () => 'ItemGrid') diff --git a/src/components/Dashboard/__tests__/__snapshots__/Dashboard.spec.js.snap b/src/components/Dashboard/__tests__/__snapshots__/Dashboard.spec.js.snap index 83e5f23de..552f134e4 100644 --- a/src/components/Dashboard/__tests__/__snapshots__/Dashboard.spec.js.snap +++ b/src/components/Dashboard/__tests__/__snapshots__/Dashboard.spec.js.snap @@ -11,7 +11,6 @@ exports[`Dashboard renders PRINT_LAYOUT dashboard 1`] = ` - @@ -75,7 +74,6 @@ exports[`Dashboard renders correctly when dashboards not loaded and id is null 1 exports[`Dashboard renders correctly when no dashboards found 1`] = ` - diff --git a/src/components/Dashboard/__tests__/__snapshots__/ViewDashboard.spec.js.snap b/src/components/Dashboard/__tests__/__snapshots__/ViewDashboard.spec.js.snap index 9609b4b87..ccb65949a 100644 --- a/src/components/Dashboard/__tests__/__snapshots__/ViewDashboard.spec.js.snap +++ b/src/components/Dashboard/__tests__/__snapshots__/ViewDashboard.spec.js.snap @@ -3,7 +3,6 @@ exports[`ViewDashboard renders correctly default 1`] = ` -
{ const [showPopover, setShowPopover] = useState(false) @@ -38,7 +40,7 @@ const FilterSelector = props => { return ( <> - + ) : null}