Skip to content

Instantly share code, notes, and snippets.

@wsmd
Last active October 29, 2017 20:00
Show Gist options
  • Select an option

  • Save wsmd/d022ebdd5d167745ab8bde3f696a4ee0 to your computer and use it in GitHub Desktop.

Select an option

Save wsmd/d022ebdd5d167745ab8bde3f696a4ee0 to your computer and use it in GitHub Desktop.

Revisions

  1. wsmd revised this gist Oct 29, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions 01 CounterButton.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import React from 'react';
    import React, { Component } from 'react';
    import classNames from 'classnames';
    import './CounterButton.css';

    @@ -13,7 +13,7 @@ const getComputedProperty = (node, property) => {
    return value;
    }

    class CounterButton extends React.Component {
    class CounterButton extends Component {
    deferredUpdates = [];

    state = {
  2. wsmd renamed this gist Oct 29, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. wsmd revised this gist Oct 29, 2017. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions CounterButton.js
    Original file line number Diff line number Diff line change
    @@ -20,7 +20,7 @@ class CounterButton extends React.Component {
    count: this.props.count,
    next: this.props.count + 1,
    prev: this.props.count - 1,
    state: null,
    animation: null,
    width: null,
    }

    @@ -40,7 +40,7 @@ class CounterButton extends React.Component {
    }

    componentWillReceiveProps(nextProps) {
    if (this.state.state === null) {
    if (this.state.animation === null) {
    this.animateNumber(nextProps.count);
    } else {
    this.deferredUpdates.push(nextProps.count);
    @@ -49,12 +49,12 @@ class CounterButton extends React.Component {

    animateNumber(nextCount) {
    if (nextCount > this.state.count) {
    this.setState({ state: 'inc', next: nextCount }, () => {
    this.setState({ animation: 'inc', next: nextCount }, () => {
    this.setState({ width: this.getWidth(this.nextNode) });
    this.updateCount(nextCount)
    });
    } else {
    this.setState({ state: 'dec', prev: nextCount }, () => {
    this.setState({ animation: 'dec', prev: nextCount }, () => {
    this.setState({ width: this.getWidth(this.prevNode) });
    this.updateCount(nextCount)
    });
    @@ -63,7 +63,7 @@ class CounterButton extends React.Component {

    updateCount(number) {
    setTimeout(() => {
    this.setState({ count: number, state: null }, this.handleDeferredItems);
    this.setState({ count: number, animation: null }, this.handleDeferredItems);
    }, this.ANIMATION_DURATION);
    }

    @@ -91,8 +91,8 @@ class CounterButton extends React.Component {

    get buttonClassName() {
    return classNames('counter', {
    incrementing: this.state.state === 'inc',
    decrementing: this.state.state === 'dec',
    incrementing: this.state.animation === 'inc',
    decrementing: this.state.animation === 'dec',
    })
    }

  4. wsmd revised this gist Oct 29, 2017. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion CounterButton.js
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@ import React from 'react';
    import classNames from 'classnames';
    import './CounterButton.css';

    const WIDTH_DIFF_THRESHOLD = 2.5;

    const PARSABLE_PROPERTIES = ['margin-left', 'animation-duration'];
    const getComputedProperty = (node, property) => {
    const value = window.getComputedStyle(node)[property];
    @@ -80,7 +82,7 @@ class CounterButton extends React.Component {
    const totalWidth = this.counter.getBoundingClientRect().width;
    const currentWidth = this.currentNode.getBoundingClientRect().width;
    const nextWidth = nextNode.getBoundingClientRect().width;
    if (Math.abs(currentWidth - nextWidth) < 2.5) {
    if (Math.abs(currentWidth - nextWidth) < WIDTH_DIFF_THRESHOLD) {
    return totalWidth;
    };
    const newWidth = totalWidth - currentWidth + nextWidth;
  5. wsmd revised this gist Oct 29, 2017. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions CounterButton.js
    Original file line number Diff line number Diff line change
    @@ -28,6 +28,7 @@ class CounterButton extends React.Component {

    this.setState({
    width: (() => {
    // @todo I should probbarly avoid a second call here
    const margin = getComputedProperty(this.counterNode, 'margin-left');
    const initial = this.counter.getBoundingClientRect().width;
    const current = this.currentNode.getBoundingClientRect().width;
  6. wsmd created this gist Oct 29, 2017.
    24 changes: 24 additions & 0 deletions App.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,24 @@
    import React, { Component } from 'react';
    import CounterButton from './CounterButton';
    import TodoList from './TodoList'; // simple to do list with onCheck and onUncheck props

    // See live demo:
    // https://react-counter-button.herokuapp.com/

    class App extends Component {
    state = {
    counter: 0,
    }

    // some logic to handle state.counter

    render() {
    return (
    <div className="App">
    <CounterButton count={this.state.counter}>Archive</CounterButton>
    </div>
    );
    }
    }

    export default App;
    104 changes: 104 additions & 0 deletions CounterButton.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    .counter {
    color: white;
    background: #0076FF;
    box-shadow: 0 4px 8px -3px rgba(0, 118, 255, 0.5), 0 1px 1px rgba(0, 118, 255, 0.25);
    padding: 8px 12px;
    line-height: 16px;
    display: inline-block;
    border-radius: 4px;
    transition: all 0.15s ease;
    font-size: 14px;
    box-sizing: border-box;
    cursor: pointer;
    text-align: left;
    border: 0;
    }

    .counter-counts {
    margin-left: 8px;
    position: absolute;
    display: inline-block;
    text-align: center;
    opacity: 0.75;
    }

    .counter-count {
    transition: all .2s ease;
    display: inline-block;
    }

    .counter-count--active {
    transform: translateY(0px);
    }

    .counter-count--prev,
    .counter-count--next {
    position: absolute;
    left: 0;
    opacity: 0;
    }

    .counter-count--prev {
    transform: translateY(25px);
    }

    .counter-count--next {
    transform: translateY(-25px);
    }

    @keyframes incrementingNext {
    to {
    transform: translateY(0px);
    opacity: 1;
    }
    }

    @keyframes incrementingActive {
    60% {
    opacity: 0;
    }
    to {
    transform: translateY(25px);
    opacity: 0;
    }
    }

    @keyframes decrementingPrev {
    to {
    transform: translateY(0px);
    opacity: 1;
    }
    }

    @keyframes decrementingActive {
    60% {
    opacity: 0;
    }
    to {
    transform: translateY(-25px);
    opacity: 0;
    }
    }

    .counter-count {
    animation-duration: 300ms;
    animation-timing-function: ease;
    animation-fill-mode: forwards;
    animation-iteration-count: infinite;
    }

    .counter.incrementing .counter-count--next {
    animation-name: incrementingNext;
    }

    .counter.incrementing .counter-count--active {
    animation-name: incrementingActive;
    }

    .counter.decrementing .counter-count--prev {
    animation-name: decrementingPrev;
    }

    .counter.decrementing .counter-count--active {
    animation-name: decrementingActive;
    }
    129 changes: 129 additions & 0 deletions CounterButton.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,129 @@
    import React from 'react';
    import classNames from 'classnames';
    import './CounterButton.css';

    const PARSABLE_PROPERTIES = ['margin-left', 'animation-duration'];
    const getComputedProperty = (node, property) => {
    const value = window.getComputedStyle(node)[property];
    if (PARSABLE_PROPERTIES.includes(property)) {
    return parseFloat(value);
    }
    return value;
    }

    class CounterButton extends React.Component {
    deferredUpdates = [];

    state = {
    count: this.props.count,
    next: this.props.count + 1,
    prev: this.props.count - 1,
    state: null,
    width: null,
    }

    componentDidMount() {
    this.ANIMATION_DURATION =
    getComputedProperty(this.currentNode, 'animation-duration') * 1000;

    this.setState({
    width: (() => {
    const margin = getComputedProperty(this.counterNode, 'margin-left');
    const initial = this.counter.getBoundingClientRect().width;
    const current = this.currentNode.getBoundingClientRect().width;
    return initial + margin + current;
    })(),
    })
    }

    componentWillReceiveProps(nextProps) {
    if (this.state.state === null) {
    this.animateNumber(nextProps.count);
    } else {
    this.deferredUpdates.push(nextProps.count);
    }
    }

    animateNumber(nextCount) {
    if (nextCount > this.state.count) {
    this.setState({ state: 'inc', next: nextCount }, () => {
    this.setState({ width: this.getWidth(this.nextNode) });
    this.updateCount(nextCount)
    });
    } else {
    this.setState({ state: 'dec', prev: nextCount }, () => {
    this.setState({ width: this.getWidth(this.prevNode) });
    this.updateCount(nextCount)
    });
    }
    }

    updateCount(number) {
    setTimeout(() => {
    this.setState({ count: number, state: null }, this.handleDeferredItems);
    }, this.ANIMATION_DURATION);
    }

    handleDeferredItems() {
    const deferredLength = this.deferredUpdates.length;
    if (deferredLength > 0) {
    const lastDeferredNumber = this.deferredUpdates[deferredLength - 1];
    if (lastDeferredNumber !== this.state.count) {
    this.animateNumber(lastDeferredNumber);
    }
    this.deferredUpdates = [];
    }
    }

    getWidth(nextNode) {
    const totalWidth = this.counter.getBoundingClientRect().width;
    const currentWidth = this.currentNode.getBoundingClientRect().width;
    const nextWidth = nextNode.getBoundingClientRect().width;
    if (Math.abs(currentWidth - nextWidth) < 2.5) {
    return totalWidth;
    };
    const newWidth = totalWidth - currentWidth + nextWidth;
    return newWidth;
    }

    get buttonClassName() {
    return classNames('counter', {
    incrementing: this.state.state === 'inc',
    decrementing: this.state.state === 'dec',
    })
    }

    render() {
    return (
    <button
    className={this.buttonClassName}
    ref={n => { this.counter = n; }}
    style={{ width: this.state.width }}
    >
    {this.props.children}
    <div
    className="counter-counts"
    ref={n => { this.counterNode = n; }}
    >
    <span
    className="counter-count counter-count--next"
    ref={n => { this.nextNode = n; }}
    children={this.state.next}
    />
    <span
    className="counter-count counter-count--active"
    ref={n => { this.currentNode = n; }}
    children={this.state.count}
    />
    <span
    className="counter-count counter-count--prev"
    ref={n => { this.prevNode = n; }}
    children={this.state.prev}
    />
    </div>
    </button>
    );
    }
    }

    export default CounterButton;