From eebb9f632df7a72f347e2777c0083f15c8a5d1d9 Mon Sep 17 00:00:00 2001 From: rradik Date: Mon, 1 Apr 2019 10:32:35 +0530 Subject: [PATCH 1/2] changed simple countdown display to a flip countdown ref: https://codesandbox.io/s/o7y705rpp6 --- src/App.js | 22 +++-- src/Countdown.js | 150 ++++++++++++++++++++++-------- src/FlipStyles.css | 192 +++++++++++++++++++++++++++++++++++++++ src/FlipUnitContainer.js | 69 ++++++++++++++ 4 files changed, 387 insertions(+), 46 deletions(-) create mode 100644 src/FlipStyles.css create mode 100644 src/FlipUnitContainer.js diff --git a/src/App.js b/src/App.js index cce40ce..4d39ade 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,31 @@ -import React, { Component } from 'react'; -import Countdown from './Countdown.js'; -import logo from './logo.svg'; -import github from './github.png'; +import React, { Component } from "react"; +import Countdown from "./Countdown.js"; +import logo from "./logo.svg"; +import github from "./github.png"; class App extends Component { render() { const currentDate = new Date(); - const year = (currentDate.getMonth() === 11 && currentDate.getDate() > 23) ? currentDate.getFullYear() + 1 : currentDate.getFullYear(); + const year = + currentDate.getMonth() === 11 && currentDate.getDate() > 23 + ? currentDate.getFullYear() + 1 + : currentDate.getFullYear(); return (
logo -

React Countdown

+

React Flip Countdown

- + {/* */} + github View on Github
-

Christmas Eve is coming soon (Midnight of 23rd to 24th Dec, UTC time):

+

+ Christmas Eve is coming soon (Midnight of 23rd to 24th Dec, UTC time): +

); diff --git a/src/Countdown.js b/src/Countdown.js index b101841..f3f2317 100644 --- a/src/Countdown.js +++ b/src/Countdown.js @@ -1,10 +1,11 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types' +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import FlipUnitContainer from "./FlipUnitContainer.js"; /** - * Note : + * Note : * If you're using react v 15.4 or less - * You can directly import PropTypes from react instead. + * You can directly import PropTypes from react instead. * Refer to this : https://reactjs.org/warnings/dont-call-proptypes.html */ @@ -17,14 +18,20 @@ class Countdown extends Component { hours: 0, min: 0, sec: 0, - } + + daysShuffle: true, + hoursShuffle: true, + minutesShuffle: true, + secondsShuffle: true, + isExpired: false + }; } componentDidMount() { // update every second this.interval = setInterval(() => { const date = this.calculateCountdown(this.props.date); - date ? this.setState(date) : this.stop(); + date ? this.updateFlippers(date) : this.expired(); }, 1000); } @@ -32,6 +39,47 @@ class Countdown extends Component { this.stop(); } + updateFlippers(date) { + // set time units + const days = date.days; + const hours = date.hours; + const min = date.min; + const sec = date.sec; + + // on hour chanage, update hours and shuffle state + if (days !== this.state.days) { + const daysShuffle = !this.state.daysShuffle; + this.setState({ + days, + daysShuffle + }); + } + // on hour chanage, update hours and shuffle state + if (hours !== this.state.hours) { + const hoursShuffle = !this.state.hoursShuffle; + this.setState({ + hours, + hoursShuffle + }); + } + // on minute chanage, update minutes and shuffle state + if (min !== this.state.min) { + const minutesShuffle = !this.state.minutesShuffle; + this.setState({ + min, + minutesShuffle + }); + } + // on second chanage, update seconds and shuffle state + if (sec !== this.state.sec) { + const secondsShuffle = !this.state.secondsShuffle; + this.setState({ + sec, + secondsShuffle + }); + } + } + calculateCountdown(endDate) { let diff = (Date.parse(new Date(endDate)) - Date.parse(new Date())) / 1000; @@ -47,15 +95,18 @@ class Countdown extends Component { }; // calculate time difference between now and expected date - if (diff >= (365.25 * 86400)) { // 365.25 * 24 * 60 * 60 + if (diff >= 365.25 * 86400) { + // 365.25 * 24 * 60 * 60 timeLeft.years = Math.floor(diff / (365.25 * 86400)); diff -= timeLeft.years * 365.25 * 86400; } - if (diff >= 86400) { // 24 * 60 * 60 + if (diff >= 86400) { + // 24 * 60 * 60 timeLeft.days = Math.floor(diff / 86400); diff -= timeLeft.days * 86400; } - if (diff >= 3600) { // 60 * 60 + if (diff >= 3600) { + // 60 * 60 timeLeft.hours = Math.floor(diff / 3600); diff -= timeLeft.hours * 3600; } @@ -65,9 +116,16 @@ class Countdown extends Component { } timeLeft.sec = diff; + // this.updateFlippers(timeLeft); + return timeLeft; } + expired() { + this.setState({ isExpired: true }); + this.stop(); + } + stop() { clearInterval(this.interval); } @@ -75,7 +133,7 @@ class Countdown extends Component { addLeadingZeros(value) { value = String(value); while (value.length < 2) { - value = '0' + value; + value = "0" + value; } return value; } @@ -83,36 +141,52 @@ class Countdown extends Component { render() { const countDown = this.state; + const { + daysShuffle, + hoursShuffle, + minutesShuffle, + secondsShuffle, + isExpired + } = this.state; + return (
- - - {this.addLeadingZeros(countDown.days)} - {countDown.days === 1 ? 'Day' : 'Days'} - - - - - - {this.addLeadingZeros(countDown.hours)} - Hours - - - - - - - {this.addLeadingZeros(countDown.min)} - Min - - - - - - {this.addLeadingZeros(countDown.sec)} - Sec - - + {isExpired ? ( +

Hackathon Day !!

+ ) : ( +
+
+ + + + +
+
+ )}
); } diff --git a/src/FlipStyles.css b/src/FlipStyles.css new file mode 100644 index 0000000..0201164 --- /dev/null +++ b/src/FlipStyles.css @@ -0,0 +1,192 @@ +@import url("https://fonts.googleapis.com/css?family=Droid+Sans+Mono"); +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + +header { + display: flex; + position: relative; +} +header h1 { + font-family: "Droid Sans Mono", monospace; + font-weight: lighter; + text-transform: uppercase; + letter-spacing: 0.1em; + color: white; +} + +.flipClock { + display: flex; + justify-content: space-between; + width: 500px; +} + +.flipUnitContainer { + display: block; + position: relative; + width: 140px; + height: 120px; + -webkit-perspective-origin: 50% 50%; + perspective-origin: 50% 50%; + -webkit-perspective: 300px; + perspective: 300px; + background-color: white; + border-radius: 3px; + box-shadow: 0px 10px 10px -10px grey; +} + +.upperCard, +.lowerCard { + display: flex; + position: relative; + justify-content: center; + width: 100%; + height: 50%; + overflow: hidden; + border: 1px solid whitesmoke; +} +.upperCard span, +.lowerCard span { + font-size: 5em; + font-family: "Droid Sans Mono", monospace; + font-weight: lighter; + color: #333333; +} + +.upperCard { + align-items: flex-end; + border-bottom: 0.5px solid whitesmoke; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.upperCard span { + -webkit-transform: translateY(50%); + transform: translateY(50%); +} + +.lowerCard { + align-items: flex-start; + border-top: 0.5px solid whitesmoke; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.lowerCard span { + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} + +.flipCard { + display: flex; + justify-content: center; + position: absolute; + left: 0; + width: 100%; + height: 50%; + overflow: hidden; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +.flipCard span { + font-family: "Droid Sans Mono", monospace; + font-size: 5em; + font-weight: lighter; + color: #333333; +} +.flipCard.unfold { + top: 50%; + align-items: flex-start; + -webkit-transform-origin: 50% 0%; + transform-origin: 50% 0%; + -webkit-transform: rotateX(180deg); + transform: rotateX(180deg); + background-color: white; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + border: 0.5px solid whitesmoke; + border-top: 0.5px solid whitesmoke; +} +.flipCard.unfold span { + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} +.flipCard.fold { + top: 0%; + align-items: flex-end; + -webkit-transform-origin: 50% 100%; + transform-origin: 50% 100%; + -webkit-transform: rotateX(0deg); + transform: rotateX(0deg); + background-color: white; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border: 0.5px solid whitesmoke; + border-bottom: 0.5px solid whitesmoke; +} +.flipCard.fold span { + -webkit-transform: translateY(50%); + transform: translateY(50%); +} + +.fold { + -webkit-animation: fold 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s 1 + normal forwards; + animation: fold 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s 1 normal + forwards; + -webkit-transform-style: preserve-3d; + transform-style: preserve-3d; +} + +.unfold { + -webkit-animation: unfold 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s 1 + normal forwards; + animation: unfold 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s 1 normal + forwards; + -webkit-transform-style: preserve-3d; + transform-style: preserve-3d; +} + +@-webkit-keyframes fold { + 0% { + -webkit-transform: rotateX(0deg); + transform: rotateX(0deg); + } + 100% { + -webkit-transform: rotateX(-180deg); + transform: rotateX(-180deg); + } +} + +@keyframes fold { + 0% { + -webkit-transform: rotateX(0deg); + transform: rotateX(0deg); + } + 100% { + -webkit-transform: rotateX(-180deg); + transform: rotateX(-180deg); + } +} +@-webkit-keyframes unfold { + 0% { + -webkit-transform: rotateX(180deg); + transform: rotateX(180deg); + } + 100% { + -webkit-transform: rotateX(0deg); + transform: rotateX(0deg); + } +} +@keyframes unfold { + 0% { + -webkit-transform: rotateX(180deg); + transform: rotateX(180deg); + } + 100% { + -webkit-transform: rotateX(0deg); + transform: rotateX(0deg); + } +} diff --git a/src/FlipUnitContainer.js b/src/FlipUnitContainer.js new file mode 100644 index 0000000..1cc9420 --- /dev/null +++ b/src/FlipUnitContainer.js @@ -0,0 +1,69 @@ +import React from "react"; +import "./FlipStyles.css"; + +// function component +const AnimatedCard = ({ animation, digit }) => { + return ( +
+ {digit} +
+ ); +}; + +// function component +const StaticCard = ({ position, digit }) => { + return ( +
+ {digit} +
+ ); +}; + +// function component +const FlipUnitContainer = ({ + digit, + currentDigit, + previousDigit, + shuffle, + unit +}) => { + // assign digit values + // let currentDigit = digit; + // let previousDigit = digit + 1; + + // to prevent a negative value + // if (unit !== "hours") { + // previousDigit = previousDigit === -1 ? 59 : previousDigit; + // } else { + // previousDigit = previousDigit === -1 ? 23 : previousDigit; + // } + + // // add zero + // if (currentDigit < 10) { + // currentDigit = `0${currentDigit}`; + // } + // if (previousDigit < 10) { + // previousDigit = `0${previousDigit}`; + // } + + // shuffle digits + const digit1 = shuffle ? previousDigit : currentDigit; + const digit2 = !shuffle ? previousDigit : currentDigit; + + // shuffle animations + const animation1 = shuffle ? "fold" : "unfold"; + const animation2 = !shuffle ? "fold" : "unfold"; + + return ( +
+ + + + +
+ {unit} +
+ ); +}; + +export default FlipUnitContainer; From 8bd0c32dfa64407b393c7908f3f1490bcba539b7 Mon Sep 17 00:00:00 2001 From: rradik Date: Mon, 1 Apr 2019 16:26:12 +0530 Subject: [PATCH 2/2] props based switching between default plain view and new flip view flip view is based on 'React Flip Clock' pen by Libor Gabrhel (https://codepen.io/Libor_G/pen/JyJzjb) --- src/App.js | 12 +++- src/Countdown.js | 142 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 112 insertions(+), 42 deletions(-) diff --git a/src/App.js b/src/App.js index 4d39ade..c349f4b 100644 --- a/src/App.js +++ b/src/App.js @@ -16,8 +16,10 @@ class App extends Component { logo

React Flip Countdown

- {/* */} - + github View on Github @@ -26,7 +28,11 @@ class App extends Component {

Christmas Eve is coming soon (Midnight of 23rd to 24th Dec, UTC time):

- + ); } diff --git a/src/Countdown.js b/src/Countdown.js index f3f2317..439a517 100644 --- a/src/Countdown.js +++ b/src/Countdown.js @@ -23,15 +23,22 @@ class Countdown extends Component { hoursShuffle: true, minutesShuffle: true, secondsShuffle: true, + isExpired: false }; + + this.addLeadingZeros = this.addLeadingZeros.bind(this); } componentDidMount() { // update every second this.interval = setInterval(() => { const date = this.calculateCountdown(this.props.date); - date ? this.updateFlippers(date) : this.expired(); + date + ? this.props.useFlipView + ? this.updateFlippers(date) + : this.setState(date) + : this.expired(); }, 1000); } @@ -116,8 +123,6 @@ class Countdown extends Component { } timeLeft.sec = diff; - // this.updateFlippers(timeLeft); - return timeLeft; } @@ -140,58 +145,117 @@ class Countdown extends Component { render() { const countDown = this.state; - const { + isExpired, daysShuffle, hoursShuffle, minutesShuffle, - secondsShuffle, - isExpired + secondsShuffle } = this.state; return (
{isExpired ? ( -

Hackathon Day !!

+ + ) : this.props.useFlipView ? ( + ) : ( -
-
- - - - -
-
+ )}
); } } +function ExpiryView(props) { + return

{props.expiryMsg}

; +} + +function DefaultPlainView(props) { + const { countDown, addLeadingZeros } = props; + + return ( +
+ + + {addLeadingZeros(countDown.days)} + {countDown.days === 1 ? "Day" : "Days"} + + + + + + {addLeadingZeros(countDown.hours)} + Hours + + + + + + {addLeadingZeros(countDown.min)} + Min + + + + + + {addLeadingZeros(countDown.sec)} + Sec + + +
+ ); +} + +function FlipView(props) { + const { countDown, addLeadingZeros, shuffle } = props; + + return ( +
+ + + + +
+ ); +} + Countdown.propTypes = { date: PropTypes.string.isRequired };