Skip to content

Commit

Permalink
Refactorings, doc updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ndresx committed Oct 11, 2020
1 parent b2845da commit 6ed93fd
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 62 deletions.
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,31 @@ This component also considers the child that may live within the `<Countdown></C

_Please note that once a custom `renderer` is defined, the [`children`](#children) prop will be ignored._

<a name="renderer"></a>
### `renderer(props)`
The component's render output is very simple and depends on [`daysInHours`](#daysinhours): _{days}:{hours}:{minutes}:{seconds}_.
If this doesn't fit your needs, a custom `renderer` callback can be defined to return a new React element. It receives an argument that consists of a time delta object (incl. `formatted` values) to build your own representation of the countdown.
### `renderer`
The component's raw render output is kept very simple.

For more advanced countdown displays, a custom `renderer` callback can be defined to return a new React element. It receives the following [render props](#render-props) as the first argument.

#### Render Props

The render props object consists of the current time delta object, the countdown's [`api`](#api-reference), the component [`props`](#props), and last but not least, a [`formatted`](#formattimedelta) object.

```js
{ total, days, hours, minutes, seconds, milliseconds, completed }
{
total: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
completed: true,
api: { ... },
props: { ... },
formatted: { ... }
}
```

The render props also contain the countdown's [`API`](#api-reference) as `api` prop as well as the passed in component [`props`](#props).

_Please note that once a custom `renderer` is defined, the [`children`](#children) prop will be ignored._
> Please note that a defined custom [`renderer`](#renderer) will ignore the [`children`](#children) prop.
### `now`
If the current date and time (determined via a reference to `Date.now`) is not the right thing to compare with for you, a reference to a custom function that returns a similar dynamic value could be provided as an alternative.
Expand Down Expand Up @@ -287,20 +301,26 @@ Defines whether the calculated value is already provided as the time difference
**`offsetTime = 0`**
Defines the offset time that gets added to the start time; only considered if controlled is false.

### `formatTimeDelta(delta, [options])`
<a name="formattimedelta"></a>
### `formatTimeDelta(timeDelta, [options])`
`formatTimeDelta` formats a given countdown time delta object. It returns the formatted portion of it, equivalent to:

```js
{ days, hours, minutes, seconds }
{
days: '00',
hours: '00',
minutes: '00',
seconds: '00',
}
```

This function accepts two arguments in total; only the first one is required.

**`delta`**
Time delta object, e.g.: returned by [`calcTimeDelta`](#calctimedelta).
**`timeDelta`**
Time delta object, e.g., returned by [`calcTimeDelta`](#calctimedelta).

**`options`**
The `options` object consists of the following three component props and is used to customize the formatting of the delta object:
The `options` object consists of the following three component props and is used to customize the time delta object's formatting:
* [`daysInHours`](#daysinhours)
* [`zeroPadTime`](#zeropadtime)
* [`zeroPadDays`](#zeropaddays)
Expand Down
8 changes: 4 additions & 4 deletions src/Countdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ describe('<Countdown />', () => {
delete computedProps.children;

const obj = wrapper.instance();
const delta = wrapper.state().timeDelta;
const { timeDelta } = wrapper.state();
expect(completionist.props).toEqual({
countdown: {
...delta,
...timeDelta,
api: obj.getApi(),
props: wrapper.props(),
formatted: formatTimeDelta(delta, { zeroPadTime }),
formatted: formatTimeDelta(timeDelta, { zeroPadTime }),
},
name: 'master',
children: 'Another child',
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('<Countdown />', () => {
expect(wrapper.state().timeDelta.total).toBe(1000);
expect(wrapper.state().timeDelta.completed).toBe(false);

// The End: onComplete callback gets triggered instead of onTick
// The End: onComplete callback gets triggered instead of the onTick callback
now.mockReturnValue(countdownDate);
jest.runTimersToTime(1000);
expect(onTick.mock.calls.length).toBe(9);
Expand Down
49 changes: 21 additions & 28 deletions src/Countdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface CountdownRenderProps extends CountdownTimeDelta {

export type CountdownRendererFn = (props: CountdownRenderProps) => React.ReactNode;

export type CountdownTimeDeltaFn = (delta: CountdownTimeDelta) => void;
export type CountdownTimeDeltaFn = (timeDelta: CountdownTimeDelta) => void;

const enum CountdownStatus {
STARTED = 'STARTED',
Expand Down Expand Up @@ -107,7 +107,6 @@ export default class Countdown extends React.Component<CountdownProps, Countdown
api: CountdownApi | undefined;

initialTimestamp = this.calcOffsetStartTimestamp();
initialTimeDelta = this.calcTimeDelta();
offsetStartTimestamp = this.props.autoStart ? 0 : this.initialTimestamp;
offsetTime = 0;

Expand Down Expand Up @@ -146,7 +145,6 @@ export default class Countdown extends React.Component<CountdownProps, Countdown
if (!this.shallowCompare(this.props, prevProps)) {
if (this.props.date !== prevProps.date) {
this.initialTimestamp = this.calcOffsetStartTimestamp();
this.initialTimeDelta = this.calcTimeDelta();
this.offsetStartTimestamp = this.initialTimestamp;
this.offsetTime = 0;
}
Expand All @@ -165,14 +163,9 @@ export default class Countdown extends React.Component<CountdownProps, Countdown
}

tick = (): void => {
const { onTick } = this.props;
const timeDelta = this.calcTimeDelta();

this.setTimeDeltaState(timeDelta, CountdownStatus.STARTED);

if (onTick && timeDelta.total > 0) {
onTick(timeDelta);
}
const callback = timeDelta.completed ? undefined : this.props.onTick;
this.setTimeDeltaState(timeDelta, undefined, callback);
};

calcTimeDelta(): CountdownTimeDelta {
Expand Down Expand Up @@ -276,34 +269,34 @@ export default class Countdown extends React.Component<CountdownProps, Countdown
status?: CountdownStatus,
callback?: (timeDelta: CountdownTimeDelta) => void
): void {
if (!this.mounted) return;

let completedCallback: this['handleOnComplete'] | undefined;

if (!this.state.timeDelta.completed && timeDelta.completed) {
this.clearTimer();
completedCallback = this.handleOnComplete;
}

if (this.mounted) {
const onDone = () => {
if (callback) callback(this.state.timeDelta);
if (completedCallback) completedCallback(this.state.timeDelta);
};
const onDone = () => {
if (callback) callback(this.state.timeDelta);
if (completedCallback) completedCallback(this.state.timeDelta);
};

return this.setState(prevState => {
let newStatus = status || prevState.status;
return this.setState(prevState => {
let newStatus = status || prevState.status;

if (timeDelta.completed) {
newStatus = CountdownStatus.COMPLETED;
} else if (!status && newStatus === CountdownStatus.COMPLETED) {
newStatus = CountdownStatus.STOPPED;
}
if (timeDelta.completed) {
newStatus = CountdownStatus.COMPLETED;
} else if (!status && newStatus === CountdownStatus.COMPLETED) {
newStatus = CountdownStatus.STOPPED;
}

return {
timeDelta,
status: newStatus,
};
}, onDone);
}
return {
timeDelta,
status: newStatus,
};
}, onDone);
}

getApi(): CountdownApi {
Expand Down
33 changes: 16 additions & 17 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,18 @@ export const timeDeltaFormatOptionsDefaults: CountdownTimeDeltaFormatOptions = {
*
* @export
* @param {Date|number|string} date Date or timestamp representation of the end date.
* @param {Object} [{ now = Date.now, precision = 0, controlled = false }={}]
* {function} [date=Date.now] Alternative function for returning the current date.
* @param {CountdownTimeDeltaOptions} [options]
* {function} [now=Date.now] Alternative function for returning the current date.
* {number} [precision=0] The precision on a millisecond basis.
* {boolean} [controlled=false] Defines whether the calculated value is already provided as the time difference or not.
* {number} [offsetTime=0] Defines the offset time that gets added to the start time; only considered if controlled is false.
* @param {number} [precision=0] The precision on a millisecond basis.
* @param {boolean} [controlled=false] Defines whether the calculated value is already provided as the time difference or not.
* @returns Object that includes details about the time difference.
* @returns Time delta object that includes details about the time difference.
*/
export function calcTimeDelta(
date: Date | string | number,
{
now = Date.now,
precision = 0,
controlled = false,
offsetTime = 0,
}: CountdownTimeDeltaOptions = {}
options: CountdownTimeDeltaOptions = {}
): CountdownTimeDelta {
const { now = Date.now, precision = 0, controlled = false, offsetTime = 0 } = options;
let startTimestamp: number;

if (typeof date === 'string') {
Expand Down Expand Up @@ -117,27 +111,32 @@ export function calcTimeDelta(
* Formats a given countdown time delta object.
*
* @export
* @param {CountdownTimeDelta} delta
* @param {CountdownTimeDelta} timeDelta The time delta object to be formatted.
* @param {CountdownTimeDeltaFormatOptions} [options]
* {boolean} [daysInHours=false] Days are calculated as hours.
* {number} [zeroPadTime=2] Length of zero-padded output, e.g.: 00:01:02
* {number} [zeroPadDays=zeroPadTime] Length of zero-padded days output, e.g.: 01
* @returns {CountdownTimeDeltaFormatted} Formatted time delta object.
*/
export function formatTimeDelta(
delta: CountdownTimeDelta,
timeDelta: CountdownTimeDelta,
options?: CountdownTimeDeltaFormatOptions
): CountdownTimeDeltaFormatted {
const { days, hours, minutes, seconds } = delta;
const { days, hours, minutes, seconds } = timeDelta;
const { daysInHours, zeroPadTime, zeroPadDays = zeroPadTime } = {
...timeDeltaFormatOptionsDefaults,
...options,
};

const zeroPadTimeLength = Math.min(2, zeroPadTime);
const formattedHours = daysInHours
? zeroPad(hours + days * 24, zeroPadTime)
: zeroPad(hours, Math.min(2, zeroPadTime));
: zeroPad(hours, zeroPadTimeLength);

return {
days: daysInHours ? '' : zeroPad(days, zeroPadDays),
hours: formattedHours,
minutes: zeroPad(minutes, Math.min(2, zeroPadTime)),
seconds: zeroPad(seconds, Math.min(2, zeroPadTime)),
minutes: zeroPad(minutes, zeroPadTimeLength),
seconds: zeroPad(seconds, zeroPadTimeLength),
};
}

0 comments on commit 6ed93fd

Please sign in to comment.