forked from sLeeNguyen/react-flip-clock-countdown
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
762fdb8
commit 46905f9
Showing
12 changed files
with
409 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
import React from 'react' | ||
import React from 'react'; | ||
|
||
import { ExampleComponent } from 'react-flip-clock-countdown' | ||
import 'react-flip-clock-countdown/dist/index.css' | ||
import FlipClockCountDown from 'react-flip-clock-countdown'; | ||
import 'react-flip-clock-countdown/dist/index.css'; | ||
|
||
const App = () => { | ||
return <ExampleComponent text="Create React Library Example 😄" /> | ||
} | ||
return <FlipClockCountDown to={new Date().getTime() + 24 * 3600 * 1000 + 5000} />; | ||
}; | ||
|
||
export default App | ||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,33 @@ | ||
* { | ||
box-sizing: border-box; | ||
} | ||
|
||
body { | ||
margin: 0; | ||
padding: 0; | ||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | ||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | ||
sans-serif; | ||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", | ||
"Droid Sans", "Helvetica Neue", sans-serif; | ||
-webkit-font-smoothing: antialiased; | ||
-moz-osx-font-smoothing: grayscale; | ||
} | ||
|
||
code { | ||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', | ||
monospace; | ||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; | ||
} | ||
|
||
html, | ||
body { | ||
height: 100vh; | ||
width: 100vw; | ||
background-color: teal; | ||
} | ||
|
||
#root { | ||
width: 100%; | ||
height: 100%; | ||
|
||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
font-family: Arial, Helvetica, sans-serif; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import React from 'react'; | ||
import clsx from 'clsx'; | ||
import { calcTimeDelta, FlipClockCountdownUnitTimeFormatted, parseTimeDelta } from './utils'; | ||
import styles from './styles.module.css'; | ||
import FlipClockDigit from './FlipClockDigit'; | ||
|
||
export interface FlipClockCountdownTimeDelta { | ||
readonly total: number; | ||
readonly days: number; | ||
readonly hours: number; | ||
readonly minutes: number; | ||
readonly seconds: number; | ||
} | ||
|
||
export interface FlipClockCountdownState { | ||
readonly timeDelta: FlipClockCountdownTimeDelta; | ||
readonly completed: boolean; | ||
} | ||
|
||
export type FlipClockCountdownTimeDeltaFn = (props: FlipClockCountdownState) => void; | ||
|
||
export interface FlipClockCountdownProps { | ||
readonly to: Date | number | string; | ||
readonly children?: React.ReactElement<any>; | ||
readonly className?: string; | ||
readonly containerProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>; | ||
readonly onComplete?: () => void; | ||
readonly onTick?: FlipClockCountdownTimeDeltaFn; | ||
} | ||
|
||
export interface FlipClockCountdownTimeDeltaFormatted { | ||
readonly days: FlipClockCountdownUnitTimeFormatted; | ||
readonly hours: FlipClockCountdownUnitTimeFormatted; | ||
readonly minutes: FlipClockCountdownUnitTimeFormatted; | ||
readonly seconds: FlipClockCountdownUnitTimeFormatted; | ||
} | ||
|
||
export interface FlipClockCountdownRenderProps extends FlipClockCountdownTimeDelta { | ||
readonly formatted: FlipClockCountdownTimeDeltaFormatted; | ||
} | ||
|
||
/** | ||
* A 3D animated flip clock countdown component for React. | ||
*/ | ||
function FlipClockCountdown(props: FlipClockCountdownProps) { | ||
const { to, className, containerProps, children, onComplete = () => {}, onTick = () => {} } = props; | ||
const [state, setState] = React.useState<FlipClockCountdownState>(constructState); | ||
const countdownRef = React.useRef(0); | ||
|
||
React.useEffect(() => { | ||
countdownRef.current = window.setInterval(tick, 1000); | ||
|
||
return () => clearTimer(); | ||
}, []); | ||
|
||
function clearTimer() { | ||
window.clearInterval(countdownRef.current); | ||
} | ||
|
||
function constructState(): FlipClockCountdownState { | ||
const timeDelta = calcTimeDelta(to); | ||
return { | ||
timeDelta, | ||
completed: timeDelta.total === 0 | ||
}; | ||
} | ||
|
||
function tick() { | ||
const newState = constructState(); | ||
setState(newState); | ||
onTick(newState); | ||
if (newState.completed) { | ||
clearTimer(); | ||
onComplete(); | ||
} | ||
} | ||
|
||
function getRenderProps(): FlipClockCountdownRenderProps { | ||
const { timeDelta } = state; | ||
return { | ||
...timeDelta, | ||
formatted: parseTimeDelta(timeDelta) | ||
}; | ||
} | ||
|
||
if (state?.completed) { | ||
return <React.Fragment>{children}</React.Fragment>; | ||
} | ||
|
||
const renderProps = getRenderProps(); | ||
const { days, hours, minutes, seconds } = renderProps.formatted; | ||
const labels = ['days', 'hours', 'minutes', 'seconds']; | ||
|
||
return ( | ||
<div {...containerProps} className={clsx(styles.fcc__container, className)}> | ||
{[days, hours, minutes, seconds].map((item, idx) => { | ||
return ( | ||
<React.Fragment key={`digit-block-${idx}`}> | ||
<div className={styles.fcc__digit_block_container}> | ||
<div className={styles.fcc__digit_block_label}>{labels[idx]}</div> | ||
{item.current.map((cItem, cIdx) => ( | ||
<FlipClockDigit key={cIdx} current={cItem} next={item.next[cIdx]} /> | ||
))} | ||
</div> | ||
{idx < 3 && <div className={styles.fcc__colon}>:</div>} | ||
</React.Fragment> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
export default FlipClockCountdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import clsx from 'clsx'; | ||
import React from 'react'; | ||
import styles from './styles.module.css'; | ||
import { Digit } from './utils'; | ||
|
||
export interface FlipClockDigitProps { | ||
current: Digit; | ||
next: Digit; | ||
className?: string; | ||
} | ||
|
||
type FlipClockDigitState = FlipClockDigitProps; | ||
|
||
export default function FlipClockDigit(props: FlipClockDigitProps) { | ||
const { current, next, className } = props; | ||
const [digit, setDigit] = React.useState<FlipClockDigitState>({ current: 0, next: 0 }); | ||
const [flipped, setFlipped] = React.useState(false); | ||
|
||
React.useEffect(() => { | ||
if (digit.current !== current) { | ||
if (digit.current === digit.next) { | ||
setDigit({ ...digit, next }); | ||
} | ||
setFlipped(true); | ||
} else { | ||
setFlipped(false); | ||
} | ||
}, [current, next]); | ||
|
||
const handleTransitionEnd = (): void => { | ||
setDigit({ current, next }); | ||
setFlipped(false); | ||
}; | ||
|
||
return ( | ||
<div className={clsx(styles.fcc__digit_block, className)}> | ||
<div className={styles.fcc__next_above}>{digit.next}</div> | ||
<div className={styles.fcc__current_below}>{digit.current}</div> | ||
<div className={clsx(styles.fcc__card, { [styles.fcc__flipped]: flipped })} onTransitionEnd={handleTransitionEnd}> | ||
<div className={clsx(styles.fcc__card_face, styles.fcc__card_face_front)}>{digit.current}</div> | ||
<div className={clsx(styles.fcc__card_face, styles.fcc__card_face_back)}>{digit.next}</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import FlipClockCountDown from '.'; | ||
|
||
describe('ExampleComponent', () => { | ||
it('is truthy', () => { | ||
expect(FlipClockCountDown).toBeTruthy(); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import FlipClockCountdown from './FlipClockCountDown'; | ||
|
||
export type { | ||
FlipClockCountdownProps, | ||
FlipClockCountdownRenderProps, | ||
FlipClockCountdownTimeDelta, | ||
FlipClockCountdownState, | ||
FlipClockCountdownTimeDeltaFormatted | ||
} from './FlipClockCountDown'; | ||
export default FlipClockCountdown; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,127 @@ | ||
/* add css module styles here (optional) */ | ||
|
||
.test { | ||
margin: 2em; | ||
padding: 0.5em; | ||
border: 2px solid #000; | ||
font-size: 2em; | ||
text-align: center; | ||
:root { | ||
--fcc-flip-duration: 0.7s; | ||
--fcc-digit-block-width: 46px; | ||
--fcc-digit-block-height: 80px; | ||
--fcc-digit-font-size: 50px; | ||
--fcc-background: #0f181a; | ||
--fcc-label-color: #ffffff; | ||
--fcc-digit-color: #ffffff; | ||
} | ||
|
||
.fcc__container { | ||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", | ||
"Droid Sans", "Helvetica Neue", sans-serif; | ||
font-size: var(--fcc-digit-font-size); | ||
color: var(--fcc-digit-color); | ||
line-height: 0; | ||
font-weight: 500; | ||
-webkit-user-select: none; | ||
-moz-user-select: none; | ||
user-select: none; | ||
cursor: default; | ||
display: flex; | ||
align-items: center; | ||
} | ||
|
||
.fcc__digit_block_container .fcc__digit_block:not(:last-child) { | ||
margin-right: 4px; | ||
} | ||
|
||
.fcc__digit_block_container { | ||
position: relative; | ||
display: flex; | ||
align-items: center; | ||
} | ||
|
||
.fcc__digit_block_label { | ||
color: var(--fcc-digit-color); | ||
line-height: 1; | ||
font-weight: 400; | ||
font-size: 16px; | ||
position: absolute; | ||
bottom: 0; | ||
left: 50%; | ||
transform: translate(-50%, 150%); | ||
text-transform: capitalize; | ||
} | ||
|
||
.fcc__digit_block { | ||
perspective: 200px; | ||
position: relative; | ||
width: var(--fcc-digit-block-width); | ||
height: var(--fcc-digit-block-height); | ||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1); | ||
border-radius: 4px; | ||
} | ||
|
||
.fcc__current_below, | ||
.fcc__next_above { | ||
position: absolute; | ||
width: 100%; | ||
height: 50%; | ||
overflow: hidden; | ||
display: flex; | ||
justify-content: center; | ||
background: var(--fcc-background); | ||
} | ||
|
||
.fcc__next_above { | ||
align-items: flex-end; | ||
top: 0; | ||
border-top-left-radius: 4px; | ||
border-top-right-radius: 4px; | ||
border-bottom: 1px solid #ffffff66; | ||
} | ||
|
||
.fcc__current_below { | ||
align-items: flex-start; | ||
bottom: 0; | ||
border-bottom-left-radius: 4px; | ||
border-bottom-right-radius: 4px; | ||
} | ||
|
||
.fcc__card { | ||
position: relative; | ||
z-index: 2; | ||
width: 100%; | ||
height: 50%; | ||
transform-style: preserve-3d; | ||
transform-origin: bottom; | ||
transform: rotateX(0); | ||
} | ||
|
||
.fcc__card.fcc__flipped { | ||
transition: transform var(--fcc-flip-duration) ease-in-out; | ||
transform: rotateX(-180deg); | ||
} | ||
|
||
.fcc__card_face { | ||
position: absolute; | ||
width: 100%; | ||
height: 100%; | ||
display: flex; | ||
justify-content: center; | ||
overflow: hidden; | ||
backface-visibility: hidden; | ||
background: var(--fcc-background); | ||
} | ||
|
||
.fcc__card_face_front { | ||
align-items: flex-end; | ||
border-top-left-radius: 4px; | ||
border-top-right-radius: 4px; | ||
border-bottom: 1px solid #ffffff66; | ||
} | ||
|
||
.fcc__card_face_back { | ||
align-items: flex-start; | ||
transform: rotateX(-180deg); | ||
border-bottom-left-radius: 4px; | ||
border-bottom-right-radius: 4px; | ||
} | ||
|
||
.fcc__colon { | ||
margin-left: 8px; | ||
margin-right: 8px; | ||
line-height: 1; | ||
} |
Oops, something went wrong.