Skip to content

Commit

Permalink
feat(zoom controls): Restyle zoom controls (#1092)
Browse files Browse the repository at this point in the history
* refactor(zoom controls): Restyle zoom controls

* chore: addressing PR comments and fixing unit tests

* chore: adding tests for ZoomControls.js

* chore: address PR comments

* chore: address PR comments

* chore: PR comments

* chore: PR comments
  • Loading branch information
ConradJChan authored and mergify[bot] committed Nov 5, 2019
1 parent 7f8b102 commit a9e66bf
Show file tree
Hide file tree
Showing 22 changed files with 527 additions and 166 deletions.
2 changes: 2 additions & 0 deletions src/i18n/en-US.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Button tooltip for rotating a preview to the left
rotate_left=Rotate left
# Tooltip for current zoom level
zoom_current_scale=Current zoom level
# Button tooltip for zooming into a preview
zoom_in=Zoom in
# Button tooltip for zooming out of a preview
Expand Down
50 changes: 29 additions & 21 deletions src/lib/Controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,40 +183,48 @@ class Controls {
};

/**
* Adds buttons to controls
* Adds element to controls
*
* @public
* @param {string} text - button text
* @param {Function} handler - button handler
* @param {string} text - text
* @param {Function} handler - on click handler
* @param {string} [classList] - optional class list
* @param {string} [buttonContent] - Optional button content HTML
* @return {void}
* @param {string} [content] - Optional content HTML
* @param {string} [tag] - Optional html tag, defaults to 'button'
* @return {HTMLElement} The created HTMLElement inserted into the control
*/
add(text, handler, classList = '', buttonContent = '') {
add(text, handler, classList = '', content = '', tag = 'button') {
const cell = document.createElement('div');
cell.className = 'bp-controls-cell';

const button = document.createElement('button');
button.setAttribute('aria-label', text);
button.setAttribute('type', 'button');
button.setAttribute('title', text);
button.className = `${CONTROLS_BUTTON_CLASS} ${classList}`;
button.addEventListener('click', handler);
const element = document.createElement(tag);
element.setAttribute('aria-label', text);
element.setAttribute('title', text);

if (tag === 'button') {
element.setAttribute('type', 'button');
element.className = `${CONTROLS_BUTTON_CLASS} ${classList}`;
element.addEventListener('click', handler);
} else {
element.className = `${classList}`;
}

if (buttonContent) {
button.innerHTML = buttonContent;
if (content) {
element.innerHTML = content;
}

cell.appendChild(button);
cell.appendChild(element);
this.controlsEl.appendChild(cell);

// Maintain a reference for cleanup
this.buttonRefs.push({
button,
handler,
});
if (handler) {
// Maintain a reference for cleanup
this.buttonRefs.push({
button: element,
handler,
});
}

return button;
return element;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/lib/Controls.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,8 @@
display: block;
}
}

.bp-zoom-current-scale {
color: $white;
font-size: 14px;
}
131 changes: 131 additions & 0 deletions src/lib/ZoomControls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import isFinite from 'lodash/isFinite';
import noop from 'lodash/noop';
import { ICON_ZOOM_IN, ICON_ZOOM_OUT } from './icons/icons';
import Controls from './Controls';

const CLASS_ZOOM_CURRENT_SCALE = 'bp-zoom-current-scale';
const CLASS_ZOOM_IN_BUTTON = 'bp-zoom-in-btn';
const CLASS_ZOOM_OUT_BUTTON = 'bp-zoom-out-btn';

class ZoomControls {
/** @property {Controls} - Controls object */
controls;

/** @property {HTMLElement} - Controls element */
controlsElement;

/** @property {number} - Current zoom scale */
currentScale;

/** @property {HTMLElement} - Current scale element */
currentScaleElement;

/** @property {number} - Max zoom scale */
maxZoom;

/** @property {number} - Min zoom scale */
minZoom;

/**
* [constructor]
*
* @param {Controls} controls - Viewer controls
* @return {ZoomControls} Instance of ZoomControls
*/
constructor(controls) {
if (!controls || !(controls instanceof Controls)) {
throw Error('controls must be an instance of Controls');
}

this.controls = controls;
this.controlsElement = controls.controlsEl;
}

/**
* Initialize the zoom controls with the initial scale and options.
*
* @param {number} currentScale - Initial scale value, assumes range on the scale of 0-1
* @param {number} [options.maxZoom] - Maximum zoom, on the scale of 0-1, though the max could be upwards of 1
* @param {number} [options.minZoom] - Minimum zoom, on the scale of 0-1
* @param {String} [options.zoomInClassName] - Class name for zoom in button
* @param {String} [options.zoomOutClassName] - Class name for zoom out button
* @param {Function} [options.onZoomIn] - Callback when zoom in is triggered
* @param {Function} [options.onZoomOut] - Callback when zoom out is triggered
* @return {void}
*/
init(
currentScale,
{
zoomOutClassName = '',
zoomInClassName = '',
minZoom = 0,
maxZoom = Number.POSITIVE_INFINITY,
onZoomIn = noop,
onZoomOut = noop,
} = {},
) {
this.maxZoom = Math.round(this.validateZoom(maxZoom, Number.POSITIVE_INFINITY) * 100);
this.minZoom = Math.round(Math.max(this.validateZoom(minZoom, 0), 0) * 100);

this.controls.add(__('zoom_out'), onZoomOut, `${CLASS_ZOOM_OUT_BUTTON} ${zoomOutClassName}`, ICON_ZOOM_OUT);
this.controls.add(
__('zoom_current_scale'),
undefined,
undefined,
`<span class="${CLASS_ZOOM_CURRENT_SCALE}" data-testid="current-zoom">100%</span>`,
'div',
);
this.controls.add(__('zoom_in'), onZoomIn, `${CLASS_ZOOM_IN_BUTTON} ${zoomInClassName}`, ICON_ZOOM_IN);

this.currentScaleElement = this.controlsElement.querySelector(`.${CLASS_ZOOM_CURRENT_SCALE}`);
this.setCurrentScale(currentScale);
}

/**
* Validates the zoom valid to ensure it is a number
*
* @param {number} zoomValue - Zoom value to validate
* @param {number} defaultZoomValue - Default zoom value
* @returns {number} The validated zoom value or the default value
*/
validateZoom(zoomValue, defaultZoomValue = 0) {
return isFinite(zoomValue) ? zoomValue : defaultZoomValue;
}

/**
* Sets the current scale
*
* @param {number} scale - New scale to be set as current, range 0-1
* @return {void}
*/
setCurrentScale(scale) {
if (!isFinite(scale)) {
return;
}

this.currentScale = Math.round(scale * 100);
this.currentScaleElement.textContent = `${this.currentScale}%`;

this.checkButtonEnablement();
}

/**
* Checks the zoom in and zoom out button enablement
*
* @return {void}
*/
checkButtonEnablement() {
const zoomOutElement = this.controlsElement.querySelector(`.${CLASS_ZOOM_OUT_BUTTON}`);
const zoomInElement = this.controlsElement.querySelector(`.${CLASS_ZOOM_IN_BUTTON}`);

if (zoomOutElement) {
zoomOutElement.disabled = this.currentScale <= this.minZoom;
}

if (zoomInElement) {
zoomInElement.disabled = this.currentScale >= this.maxZoom;
}
}
}

export default ZoomControls;
16 changes: 16 additions & 0 deletions src/lib/__tests__/Controls-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,29 @@ describe('lib/Controls', () => {
});

describe('add()', () => {
beforeEach(() => {
sandbox.stub(controls.buttonRefs, 'push');
});

it('should create a button with the right attributes', () => {
const btn = controls.add('test button', sandbox.stub(), 'test1', 'test content');
expect(btn.attributes.title.value).to.equal('test button');
expect(btn.attributes['aria-label'].value).to.equal('test button');
expect(btn.classList.contains('test1')).to.be.true;
expect(btn.innerHTML).to.equal('test content');
expect(btn.parentNode.parentNode).to.equal(controls.controlsEl);
expect(controls.buttonRefs.push).to.be.called;
});

it('should create a span if specified', () => {
const span = controls.add('test span', null, 'span1', 'test content', 'span');
expect(span.attributes.title.value).to.equal('test span');
expect(span.attributes['aria-label'].value).to.equal('test span');
expect(span.classList.contains('span1')).to.be.true;
expect(span.classList.contains('bp-controls-btn')).to.be.false;
expect(span.innerHTML).to.equal('test content');
expect(span.parentNode.parentNode).to.equal(controls.controlsEl);
expect(controls.buttonRefs.push).not.to.be.called;
});
});

Expand Down
1 change: 1 addition & 0 deletions src/lib/__tests__/ZoomControls-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="test-zoom-controls-container"></div>
Loading

0 comments on commit a9e66bf

Please sign in to comment.