Skip to content

Commit

Permalink
chore: replace d2-ui-core controlbar by moving component into dashboa…
Browse files Browse the repository at this point in the history
…rds repo (#318)

Moved the component out of d2-ui-core and simplified it to meet just our needs for dashboards.
  • Loading branch information
jenniferarnesen authored May 16, 2019
1 parent 41de2ef commit 32dd94e
Show file tree
Hide file tree
Showing 13 changed files with 566 additions and 304 deletions.
125 changes: 125 additions & 0 deletions src/components/ControlBar/ControlBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import PropTypes from 'prop-types';
import { colors } from '@dhis2/ui-core';
import { HEADERBAR_HEIGHT } from './controlBarDimensions';

import classes from './styles/ControlBar.module.css';

const DRAG_HANDLE_HEIGHT = 7;

class ControlBar extends React.Component {
constructor(props) {
super(props);

this.state = {
dragging: false,
};
}

onStartDrag = () => {
this.setState({ dragging: true });
window.addEventListener('mousemove', this.onDrag);
window.addEventListener('mouseup', this.onEndDrag);
};

onDrag = event => {
event.preventDefault();
event.stopPropagation();

const newHeight = event.clientY;

if (
this.props.onChangeHeight &&
newHeight !== this.props.height &&
newHeight > 0
) {
requestAnimationFrame(() => {
this.props.onChangeHeight(newHeight);
});
}
};

onEndDrag = () => {
this.setState({ dragging: false });
window.removeEventListener('mousemove', this.onDrag);
window.removeEventListener('mouseup', this.onEndDrag);

if (this.props.onEndDrag) {
this.props.onEndDrag();
}
};

renderDragHandle() {
return typeof this.props.onChangeHeight === 'function' ? (
<div
className={classes.draghandle}
style={{ height: DRAG_HANDLE_HEIGHT }}
onMouseDown={this.onStartDrag}
/>
) : null;
}

render() {
const height = Math.max(this.props.height, 0) + DRAG_HANDLE_HEIGHT;

const rootStyle = Object.assign(
{
height,
top: HEADERBAR_HEIGHT,
backgroundColor: this.props.editMode
? colors.yellow050
: colors.white,
paddingBottom: DRAG_HANDLE_HEIGHT,
},
// Disable animations while dragging
this.state.dragging ? { transition: 'none' } : {}
);

return (
<div style={rootStyle} className={classes.root}>
<div className={classes.content}>{this.props.children}</div>
{this.renderDragHandle()}
</div>
);
}
}

ControlBar.propTypes = {
/**
* The height of the control bar in number of lines. Must be a positive integer.
*/
height: PropTypes.number.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.
*
* If no callback is specified the control bar will not have a drag handle.
*/
onChangeHeight: PropTypes.func,

/**
* Callback function that is called when the control bar is dropped after being dragged.
* The callback receives one argument: The new height in pixels.
*
* Ignored if no "onChangeHeight" function is provided.
*/
onEndDrag: PropTypes.func,

/**
* The contents of the control bar.
*/
children: PropTypes.node.isRequired,
};

ControlBar.defaultProps = {
onChangeHeight: null,
onEndDrag: null,
};

export default ControlBar;
12 changes: 6 additions & 6 deletions src/components/ControlBar/DashboardsBar.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';
import ControlBar from '@dhis2/d2-ui-core/control-bar/ControlBar';
import ControlBar from './ControlBar';
import arraySort from 'd2-utilizr/lib/arraySort';

import Chip from './DashboardItemChip';
Expand All @@ -24,7 +24,7 @@ import { acSetFilterName } from '../../actions/dashboardsFilter';
import { orObject, orArray } from '../../modules/util';
import { apiPostControlBarRows } from '../../api/controlBar';

import './styles/ControlBar.css';
import classes from './styles/DashboardsBar.module.css';

export const MAX_ROW_COUNT = 10;

Expand All @@ -46,9 +46,10 @@ export class DashboardsBar extends Component {
}

onChangeHeight = newHeight => {
const adjustedHeight = newHeight - 52; // don't rush the transition to a bigger row count
const newRows = Math.max(
MIN_ROW_COUNT,
getNumRowsFromHeight(newHeight)
getNumRowsFromHeight(adjustedHeight)
);

if (newRows !== this.state.rows) {
Expand Down Expand Up @@ -81,7 +82,7 @@ export class DashboardsBar extends Component {
const rowCount = this.state.isMaxHeight
? MAX_ROW_COUNT
: this.state.rows;
const controlBarHeight = getControlBarHeight(rowCount, true);
const controlBarHeight = getControlBarHeight(rowCount);
const contentWrapperStyle = {
padding: `${FIRST_ROW_PADDING_HEIGHT}px 6px 0 6px`,
overflowY: this.state.isMaxHeight ? 'auto' : 'hidden',
Expand All @@ -94,10 +95,9 @@ export class DashboardsBar extends Component {
onChangeHeight={this.onChangeHeight}
onEndDrag={this.onEndDrag}
editMode={false}
expandable={true}
>
<div style={contentWrapperStyle}>
<div className="left-controls">
<div className={classes.leftControls}>
<Link
style={{
display: 'inline-block',
Expand Down
16 changes: 6 additions & 10 deletions src/components/ControlBar/EditBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 '@dhis2/d2-ui-core/control-bar/ControlBar';
import ControlBar from './ControlBar';
import TranslationDialog from '@dhis2/d2-ui-translation-dialog';
import { Button } from '@dhis2/ui-core';

Expand All @@ -27,7 +27,7 @@ import {
} from './controlBarDimensions';
import { apiFetchDashboard } from '../../api/dashboards';

import './styles/ControlBar.css';
import classes from './styles/DashboardsBar.module.css';

const buttonBarStyle = {
height: CONTROL_BAR_ROW_HEIGHT,
Expand Down Expand Up @@ -147,22 +147,18 @@ export class EditBar extends Component {
}

const { dashboardId, deleteAccess, updateAccess } = this.props;
const controlBarHeight = getControlBarHeight(MIN_ROW_COUNT, false);
const controlBarHeight = getControlBarHeight(MIN_ROW_COUNT);

const discardBtnText = updateAccess
? i18n.t('Exit without saving')
: i18n.t('Go to dashboards');

return (
<Fragment>
<ControlBar
height={controlBarHeight}
editMode={true}
expandable={false}
>
<ControlBar height={controlBarHeight} editMode={true}>
<div style={buttonBarStyle}>
{updateAccess ? (
<div className="left-controls">
<div className={classes.leftControls}>
<span style={{ marginRight: '15px' }}>
<Button primary onClick={this.onSave}>
{i18n.t('Save changes')}
Expand All @@ -188,7 +184,7 @@ export class EditBar extends Component {
</div>
) : null}

<div className="right-controls">
<div className={classes.rightControls}>
<Button secondary onClick={this.onDiscard}>
{discardBtnText}
</Button>
Expand Down
133 changes: 133 additions & 0 deletions src/components/ControlBar/__tests__/ControlBar.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import { shallow } from 'enzyme';

import ControlBar from '../ControlBar';

jest.mock('@dhis2/ui-core', () => {
return {
colors: { white: 'white', yellow050: 'yellow' },
};
});

describe('ControlBar', () => {
let props;
const children = (
<div>
<p>Child</p>
<p>elements</p>
</div>
);

const renderControlBar = () =>
shallow(<ControlBar {...props}>{children}</ControlBar>);

const requestAnimationFrame = callback => callback();

beforeEach(() => {
props = {
height: 32,
editMode: false,
};
});

it('should render a div', () => {
expect(renderControlBar().type()).toBe('div');
});

it('should contain the specified children', () => {
expect(renderControlBar().contains(children)).toBe(true);
});

it('should render the drag handle when onChangeHeight callback is specified', () => {
props.onChangeHeight = jest.fn();
const controlBar = renderControlBar();

expect(controlBar.children().length).toBe(2);
expect(controlBar.childAt(1).prop('className')).toBe('draghandle');
});

it('should not render the drag handle when no onChangeHeight callback is specified', () => {
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;

props.onChangeHeight = jest.fn();
const dragFlap = renderControlBar().childAt(1);

dragFlap.simulate('mousedown');
window.dispatchEvent(new MouseEvent('mousemove', { clientY: 1234 }));
window.dispatchEvent(new Event('mouseup'));

expect(props.onChangeHeight.mock.calls.length).toBe(1);
});

it('should only call the onChangeHeight callback if the height actually changed', () => {
// Replace the async DOM function with a sync function that immediately executes the callback
window.requestAnimationFrame = requestAnimationFrame;

props.onChangeHeight = jest.fn();
const initialHeight = 1234;
const randomMousePosition = 4321;
props.height = initialHeight;

const dragFlap = renderControlBar().childAt(1);

dragFlap.simulate('mousedown');
window.dispatchEvent(
new MouseEvent('mousemove', { clientY: randomMousePosition })
);
expect(props.onChangeHeight.mock.calls.length).toBe(1);
const newHeight = initialHeight - props.onChangeHeight.mock.calls[0][0];

window.dispatchEvent(
new MouseEvent('mousemove', {
clientY: newHeight + randomMousePosition,
})
);
window.dispatchEvent(new Event('mouseup'));

expect(props.onChangeHeight.mock.calls.length).toBe(1);
});

it('should disable CSS transitions while dragging', () => {
props.onChangeHeight = jest.fn();
const controlBar = renderControlBar();
const dragFlap = controlBar.childAt(1);

expect(controlBar.props().style.transition).not.toBe('none');
dragFlap.simulate('mousedown');
expect(controlBar.props().style.transition).toBe('none');

window.dispatchEvent(new MouseEvent('mouseup'));
controlBar.update();
expect(controlBar.props().style.transition).not.toBe('none');
});

it('should start listening for mousemove and mouseup events when the drag flap is clicked', () => {
const spy = jest.spyOn(window, 'addEventListener');

props.onChangeHeight = jest.fn();
const dragFlap = renderControlBar().childAt(1);

dragFlap.last().simulate('mousedown');
const eventHandlers = window.addEventListener.mock.calls.map(
args => args[0]
);
expect(eventHandlers.includes('mousemove')).toBe(true);
expect(eventHandlers.includes('mouseup')).toBe(true);

spy.mockReset();
spy.mockRestore();
});
});
16 changes: 2 additions & 14 deletions src/components/ControlBar/__tests__/DashboardsBar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,6 @@ import ShowMoreButton from '../ShowMoreButton';
import DashboardItemChip from '../DashboardItemChip';
import * as api from '../../../api/controlBar';

jest.mock('@dhis2/d2-ui-core/control-bar/ControlBar', () => props => {
return (
<div
className="mock-dhis2-controlbar"
onChangeHeight={val => props.onChangeHeight(val)}
onEndDrag={props.onEndDrag}
>
{props.children}
</div>
);
});

describe('DashboardsBar', () => {
let props;
let shallowDashboardsBar;
Expand Down Expand Up @@ -89,10 +77,10 @@ describe('DashboardsBar', () => {
props.userRows = MAX_ROW_COUNT - 1;
const dbr = dashboardsBar();

const newPixelHeight = 200; // should be equivalent to 4 rows
const newPixelHeight = 200; // should be equivalent to 3 rows
dbr.simulate('changeHeight', newPixelHeight);
expect(props.onChangeHeight).toHaveBeenCalled();
expect(props.onChangeHeight).toHaveBeenCalledWith(4);
expect(props.onChangeHeight).toHaveBeenCalledWith(3);
});

it('does not trigger onChangeHeight when controlbar height is changed to similar value', () => {
Expand Down
Loading

0 comments on commit 32dd94e

Please sign in to comment.