├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .storybook
├── main.js
└── preview.js
├── .travis.yml
├── LICENSE
├── README.md
├── __tests__
├── .eslintrc.json
├── extended-format.test.js
├── helpers
│ └── index.js
├── optional-format.test.js
├── other-languages.test.js
├── timer-format.test.js
└── unit-durations.test.js
├── index.js
├── index.stories.js
├── internals
└── testing
│ └── test-setup.js
├── messages.js
├── package-lock.json
├── package.json
└── rollup.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env", {
4 | "modules": false,
5 | "targets": "last 2 versions, ie 11"
6 | }],
7 | "@babel/react"
8 | ],
9 | "env": {
10 | "test": {
11 | "presets": ["@babel/env", "@babel/react"]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:react/recommended"
6 | ],
7 | "env": {
8 | "es6": true
9 | },
10 | "parserOptions": {
11 | "ecmaVersion": 2020,
12 | "sourceType": "module",
13 | "ecmaFeatures": {
14 | "jsx": true
15 | }
16 | },
17 | "plugins": [
18 | "react"
19 | ],
20 | "rules": {
21 | "comma-dangle": ["error", {
22 | "arrays": "always-multiline",
23 | "objects": "always-multiline",
24 | "functions": "always-multiline"
25 | }],
26 | "react/display-name": 0,
27 | "react/prop-types": 0
28 | },
29 | "settings": {
30 | "react": {
31 | "version": "16.0"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | dist
4 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../**/*.stories.[tj]s'],
3 | addons: ['storybook-addon-intl/register'],
4 | };
5 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import { addDecorator } from "@storybook/react";
2 | import { setIntlConfig, withIntl } from "storybook-addon-intl";
3 |
4 | const locales = ["en", "ja"];
5 |
6 | // Provide your messages, or you can import local locale messages files.
7 | const messages = {
8 | 'en': {},
9 | 'ja': {
10 | 'react-intl-formatted-duration.longFormatting': '{minutes}{seconds}',
11 | 'react-intl-formatted-duration.duration': '{value}{unit}',
12 | 'react-intl-formatted-duration.daysUnit': '日',
13 | 'react-intl-formatted-duration.hoursUnit': '時',
14 | 'react-intl-formatted-duration.minutesUnit': '分',
15 | 'react-intl-formatted-duration.secondsUnit': '秒',
16 | },
17 | };
18 |
19 | // Set intl configuration
20 | setIntlConfig({
21 | defaultLocale: "en",
22 | locales,
23 | getMessages: locale => messages[locale],
24 | });
25 |
26 | // Register decorator
27 | addDecorator(withIntl);
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 12
4 | git:
5 | depth: 3
6 | cache: npm
7 | notifications:
8 | email: false
9 | script:
10 | - npm run lint
11 | - npm run test:coverage
12 | after_success:
13 | - test -f ./coverage/lcov.info && npm run codecov
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 AIR en-japan
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Formatted Duration
2 |
3 | [react-intl](https://github.com/yahoo/react-intl) is an amazing library providing React components and API to localize your application, however it lacks a `Duration` component.
4 |
5 | If you want to show the time it takes to do something like `1 minute` or `5 minutes` or even a simple timer `0:30` you're [out of luck](https://github.com/yahoo/react-intl/issues/77) because the ECMA committee hasn't specified the [DurationFormat](https://github.com/tc39/ecma402/issues/47) yet.
6 |
7 | This component provides a very simple abstraction that works on React (DOM), React Native and any other target environment to format simple durations.
8 |
9 | ## Usage
10 |
11 | `npm i --save react-intl-formatted-duration`
12 |
13 | ### Extended format
14 |
15 | ```js
16 | // Using React DOM
17 | import React from 'react';
18 | import FormattedDuration from 'react-intl-formatted-duration';
19 |
20 | export default MyComponent() {
21 | return
22 | // will render `1 minute`
23 | }
24 | ```
25 |
26 | The default format only shows minutes and seconds. For more complex needs check the [custom format section](#Custom_format).
27 |
28 | By default the output text is wrapped in a `span`, you can specify any component you like available on your target environment:
29 |
30 | ```js
31 | // Using React Native
32 | import React from 'react';
33 | import FormattedDuration from 'react-intl-formatted-duration';
34 |
35 | import { Text } from 'react-native';
36 |
37 | export default MyComponent() {
38 | return
39 | // will render `1 minute`
40 | }
41 | ```
42 |
43 | ```js
44 | // Using styled components
45 | import React from 'react';
46 | import FormattedDuration from 'react-intl-formatted-duration';
47 |
48 | import styled from 'styled-components';
49 | const Text = styled.span``;
50 |
51 | export default MyComponent() {
52 | return
53 | // will render `1 minute`
54 | }
55 | ```
56 |
57 | #### Styling numbers
58 |
59 | If you want to style numbers differently from text you can pass a `valueComponent`
60 |
61 | ```js
62 |
63 |
64 | // renders
65 |
66 | 1 minute 30 seconds
67 | ```
68 |
69 | Having different components is useful not only for styling. Some languages use different [numbering systems](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat). For example Japanese has full-width numbers, so you might want to render `10分` instead of `10分`, to do so you can use:
70 |
71 | ```js
72 | import React from 'react';
73 | import { FormattedNumber } from 'react-intl';
74 | import FormattedDuration from 'react-intl-formatted-duration';
75 |
76 | /*
77 | You'll also need to select Japanese locale and configure the IntlProvider to use
78 | `ja-JP-u-nu-fullwide`
79 |
80 | Somewhere in you application
81 | import { IntlProvider } from 'react-intl';
82 |
85 | */
86 |
87 | export default MyComponent() {
88 | return
89 | // will render `10分`
90 | }
91 | ```
92 |
93 | ### Custom format
94 |
95 | #### Hours and days
96 |
97 | By default the component only renders minutes and seconds, if you want to display hours or days you can use a custom format:
98 |
99 | ```js
100 |
101 | // will render `2 days 2 hours`
102 |
103 |
104 | // will render `50 hours`
105 |
106 |
107 | // will render `3000 minutes`
108 | }
109 | ```
110 |
111 | Seconds is also optional and if missing, minutes will be rounded to the closed value
112 |
113 | ```js
114 |
115 | // will render `0 minutes`
116 |
117 |
118 | // will render `1 minute`
119 |
120 |
121 | // will render `1 minute`
122 | ```
123 |
124 | The custom format can itself be localized by passing a message id instead of the actual value
125 |
126 | ```js
127 | import React from 'react';
128 | import FormattedDuration from 'react-intl-formatted-duration';
129 |
130 | import messages from './messages';
131 |
132 | export default MyComponent() {
133 | return (
134 |
138 | );
139 | }
140 | ```
141 |
142 | #### Unit display
143 |
144 | While `format` allows to select which units to render, `unitDisplay` allows to configure the way each unit is rendered.
145 |
146 | ```js
147 |
148 | // will render `1 minute`
149 |
150 |
151 | // will render `1 min`
152 |
153 |
154 | // will render `1m`
155 | ```
156 |
157 | #### Timer format
158 |
159 | ```js
160 | import FormattedDuration, { TIMER_FORMAT } from 'react-intl-formatted-duration';
161 |
162 | export default MyComponent() {
163 | return
164 | // will render `1:00`
165 | }
166 | ```
167 |
168 | ## Localization
169 |
170 | `react-intl-formatted-duration` expects the following keys inside your translation file
171 |
172 | * `react-intl-formatted-duration.longFormatting` the default format that generates something like `1 minute 30 seconds`. It uses the values `{days}`, `{hours}`, `{minutes}` and `{seconds}`. For example you can change it to `{minutes} and {seconds}`.
173 | * `react-intl-formatted-duration.duration` the format used by the `minutes` and `seconds` variables described above. It uses the values `{value}` and `{unit}`. The default is `{value} {unit}` where `value` is a number and `{unit}` is the textual unit like `minute(s)` or `second(s)`.
174 | * `react-intl-formatted-duration.timerFormatting` format for `TIMER_FORMAT`, defaults to `{minutes}:{seconds}` where both values are numbers padded to have a minimum length of 2 characters
175 | * `react-intl-formatted-duration.daysUnit` string for formatting days, default `{value, plural, one {day} other {days}}`
176 | * `react-intl-formatted-duration.hoursUnit` string for formatting hours, default `{value, plural, one {hour} other {hours}}`
177 | * `react-intl-formatted-duration.minutesUnit` string for formatting minutes, default `{value, plural, one {minute} other {minutes}}`
178 | * `react-intl-formatted-duration.secondsUnit` string for formatting seconds, default `{value, plural, one {second} other {seconds}}`
179 |
180 | The messages for `daysUnit`, `hoursUnit`, `minutesUnit`, `secondsUnit` use the [format-js syntax](https://formatjs.io/guides/message-syntax/) and are only used when `unitDisplay` is not specified.
181 |
182 | If you're using the `extract-intl` script from [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) you can import `react-intl-formatted-duration/messages` to automatically generate the keys in your translation files.
183 |
184 |
185 | ## Upgrading from version 1.0
186 |
187 | Version `2.x` allows to use the whole power of format-js message syntax. All you need to do is remove all keys like `daysSingular`, `dayPlural` and simply use `daysUnit` with the format described above.
188 |
189 | ## Upgrading from version 2.0
190 |
191 | Version `3.x` has exactly the same API as version `2.x` but is a complete rewrite. You don't need to change your code.
192 |
193 | ## Upgrading from version 3.0
194 |
195 | Version `4.x` doesn't change any of the default behavior of version `3.x` and only contains new features. However internally it bumps the version of `intl-unofficial-duration-unit-format` from `1.x` to `3.x` which now requires `Intl.NumberFormat` to be available globally. If you've installed `react-intl` correctly, chances are you don't need to change your code.
196 |
--------------------------------------------------------------------------------
/__tests__/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/__tests__/extended-format.test.js:
--------------------------------------------------------------------------------
1 | import { text } from './helpers';
2 |
3 | describe('Extended format', () => {
4 | it('formats duration in English with long format (react)', () => {
5 | expect(text(1)).toEqual('1 second');
6 | expect(text(30)).toEqual('30 seconds');
7 | expect(text(59)).toEqual('59 seconds');
8 | expect(text(60)).toEqual('1 minute');
9 | expect(text(61)).toEqual('1 minute 1 second');
10 | expect(text(62)).toEqual('1 minute 2 seconds');
11 | expect(text(120)).toEqual('2 minutes');
12 | expect(text(121)).toEqual('2 minutes 1 second');
13 | expect(text(150)).toEqual('2 minutes 30 seconds');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/__tests__/helpers/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {IntlProvider} from 'react-intl';
3 | import {mount} from 'enzyme';
4 | import DurationMessage from '../../index';
5 |
6 | let currentLocale = 'en';
7 |
8 | const messages = {
9 | en: {},
10 | ja: {
11 | "react-intl-formatted-duration/custom-format/ja-full-test-format": "{days} {hours} {minutes} {seconds}",
12 | "react-intl-formatted-duration.duration": "{value}{unit}",
13 | "react-intl-formatted-duration.secondsUnit": "{value, plural, other {秒}}",
14 | "react-intl-formatted-duration.minutesUnit": "{value, plural, other {分}}",
15 | "react-intl-formatted-duration.hoursUnit": "{value, plural, other {時}}",
16 | "react-intl-formatted-duration.daysUnit": "{value, plural, other {日}}",
17 | },
18 | };
19 |
20 | export function setLocale(locale) {
21 | currentLocale = locale;
22 | }
23 |
24 | function mountWithIntl(node) {
25 | return mount(node, {
26 | wrappingComponent: IntlProvider,
27 | wrappingComponentProps: {
28 | locale: currentLocale,
29 | defaultLocale: currentLocale,
30 | messages: messages[currentLocale],
31 | },
32 | });
33 | }
34 |
35 | function Text(props) {
36 | return ;
37 | }
38 |
39 | export function text(value, format) {
40 | const unitDisplay = ['long', 'short', 'narrow'].includes(format) ? format : undefined;
41 | return mountWithIntl((
42 |
48 | )).text().trim().replace(/\s+/g, ' ');
49 | }
50 |
--------------------------------------------------------------------------------
/__tests__/optional-format.test.js:
--------------------------------------------------------------------------------
1 | import { text } from './helpers';
2 |
3 | describe('Optional format', () => {
4 | it('formats duration using hours', () => {
5 | const format = '{hours} {minutes} {seconds}';
6 | expect(text(1, format)).toEqual('1 second');
7 | expect(text(60, format)).toEqual('1 minute');
8 | expect(text(61, format)).toEqual('1 minute 1 second');
9 | expect(text(3600, format)).toEqual('1 hour');
10 | expect(text(3601, format)).toEqual('1 hour 1 second');
11 | expect(text(3602, format)).toEqual('1 hour 2 seconds');
12 | expect(text(3660, format)).toEqual('1 hour 1 minute');
13 | expect(text(3661, format)).toEqual('1 hour 1 minute 1 second');
14 | expect(text(7322, format)).toEqual('2 hours 2 minutes 2 seconds');
15 | expect(text(90000, format)).toEqual('25 hours');
16 | });
17 |
18 | it('formats duration using days and ignoring seconds', () => {
19 | const format = '{days} {hours} {minutes}';
20 | expect(text(1, format)).toEqual('0 minutes');
21 | expect(text(60, format)).toEqual('1 minute');
22 | expect(text(61, format)).toEqual('1 minute');
23 | expect(text(110, format)).toEqual('2 minutes');
24 | expect(text(3600, format)).toEqual('1 hour');
25 | expect(text(3601, format)).toEqual('1 hour');
26 | expect(text(3660, format)).toEqual('1 hour 1 minute');
27 | expect(text(3661, format)).toEqual('1 hour 1 minute');
28 | expect(text(7322, format)).toEqual('2 hours 2 minutes');
29 | expect(text(86400, format)).toEqual('1 day');
30 | expect(text(90000, format)).toEqual('1 day 1 hour');
31 | expect(text(180000, format)).toEqual('2 days 2 hours');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/__tests__/other-languages.test.js:
--------------------------------------------------------------------------------
1 | import {setLocale, text} from './helpers';
2 |
3 | describe('Optional format', () => {
4 | it('formats duration using hours', () => {
5 | setLocale('ja');
6 |
7 | const format = 'ja-full-test-format';
8 | expect(text(1, format)).toEqual('1秒');
9 | expect(text(60, format)).toEqual('1分');
10 | expect(text(61, format)).toEqual('1分 1秒');
11 | expect(text(3600, format)).toEqual('1時');
12 | expect(text(3601, format)).toEqual('1時 1秒');
13 | expect(text(3602, format)).toEqual('1時 2秒');
14 | expect(text(3660, format)).toEqual('1時 1分');
15 | expect(text(3661, format)).toEqual('1時 1分 1秒');
16 | expect(text(7322, format)).toEqual('2時 2分 2秒');
17 | expect(text(90000, format)).toEqual('1日 1時');
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/__tests__/timer-format.test.js:
--------------------------------------------------------------------------------
1 | import { text } from './helpers';
2 |
3 | import { TIMER_FORMAT } from '../index';
4 |
5 | describe('Timer format', () => {
6 | it('formats duration in English with timer formar', () => {
7 | expect(text(1, TIMER_FORMAT)).toEqual('0:01');
8 | expect(text(30, TIMER_FORMAT)).toEqual('0:30');
9 | expect(text(59, TIMER_FORMAT)).toEqual('0:59');
10 | expect(text(60, TIMER_FORMAT)).toEqual('1:00');
11 | expect(text(61, TIMER_FORMAT)).toEqual('1:01');
12 | expect(text(62, TIMER_FORMAT)).toEqual('1:02');
13 | expect(text(120, TIMER_FORMAT)).toEqual('2:00');
14 | expect(text(121, TIMER_FORMAT)).toEqual('2:01');
15 | expect(text(150, TIMER_FORMAT)).toEqual('2:30');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/__tests__/unit-durations.test.js:
--------------------------------------------------------------------------------
1 | import { text } from './helpers';
2 |
3 | describe('Extended format', () => {
4 | it('formats duration using CLDR unitDisplay (long)', () => {
5 | expect(text(1, 'long')).toEqual('1 second');
6 | expect(text(30, 'long')).toEqual('30 seconds');
7 | expect(text(59, 'long')).toEqual('59 seconds');
8 | expect(text(60, 'long')).toEqual('1 minute');
9 | expect(text(61, 'long')).toEqual('1 minute 1 second');
10 | expect(text(62, 'long')).toEqual('1 minute 2 seconds');
11 | expect(text(120, 'long')).toEqual('2 minutes');
12 | expect(text(121, 'long')).toEqual('2 minutes 1 second');
13 | expect(text(150, 'long')).toEqual('2 minutes 30 seconds');
14 | });
15 |
16 | it('formats duration using CLDR unitDisplay (short)', () => {
17 | expect(text(1, 'short')).toEqual('1 sec');
18 | expect(text(30, 'short')).toEqual('30 sec');
19 | expect(text(59, 'short')).toEqual('59 sec');
20 | expect(text(60, 'short')).toEqual('1 min');
21 | expect(text(61, 'short')).toEqual('1 min 1 sec');
22 | expect(text(62, 'short')).toEqual('1 min 2 sec');
23 | expect(text(120, 'short')).toEqual('2 min');
24 | expect(text(121, 'short')).toEqual('2 min 1 sec');
25 | expect(text(150, 'short')).toEqual('2 min 30 sec');
26 | });
27 |
28 | it('formats duration using CLDR unitDisplay (narrow)', () => {
29 | expect(text(1, 'narrow')).toEqual('1s');
30 | expect(text(30, 'narrow')).toEqual('30s');
31 | expect(text(59, 'narrow')).toEqual('59s');
32 | expect(text(60, 'narrow')).toEqual('1m');
33 | expect(text(61, 'narrow')).toEqual('1m 1s');
34 | expect(text(62, 'narrow')).toEqual('1m 2s');
35 | expect(text(120, 'narrow')).toEqual('2m');
36 | expect(text(121, 'narrow')).toEqual('2m 1s');
37 | expect(text(150, 'narrow')).toEqual('2m 30s');
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Unfortunately ReactIntl doesn't support duration yet because it's not implemented
3 | * in the ECMA specs: https://github.com/yahoo/react-intl/issues/77
4 | *
5 | * becomes 30 seconds
6 | * becomes 1 minute
7 | * becomes 2 minutes 30 seconds
8 | * `minutes` and `seconds` are translated
9 | *
10 | * becomes 0:30
11 | * becomes 1:00
12 | * becomes 2:30
13 | */
14 |
15 | import React from 'react';
16 | import { injectIntl } from 'react-intl';
17 | import DurationUnitFormat from 'intl-unofficial-duration-unit-format';
18 |
19 | export const EXTENDED_FORMAT = 'EXTENDED_FORMAT';
20 | export const TIMER_FORMAT = 'TIMER_FORMAT';
21 |
22 | function DurationMessage({ intl, seconds, format, textComponent, unitDisplay, valueComponent, ...otherProps }) {
23 | let actualFormat = intl.messages[`react-intl-formatted-duration/custom-format/${format || ''}`] || format;
24 | if (!format || format === EXTENDED_FORMAT) {
25 | actualFormat = intl.messages['react-intl-formatted-duration.longFormatting'] || '{minutes} {seconds}';
26 | }
27 | if (format === TIMER_FORMAT) {
28 | actualFormat = intl.messages['react-intl-formatted-duration.timerFormatting'] || '{minutes}:{seconds}';
29 | }
30 | let actualSytle = unitDisplay;
31 | if (!actualSytle) {
32 | actualSytle = format === TIMER_FORMAT ? DurationUnitFormat.styles.TIMER : DurationUnitFormat.styles.CUSTOM;
33 | }
34 | const parts = new DurationUnitFormat(intl.locale, {
35 | format: actualFormat,
36 | formatUnits: {
37 | [DurationUnitFormat.units.DAY]: intl.messages['react-intl-formatted-duration.daysUnit'] || '{value, plural, one {day} other {days}}',
38 | [DurationUnitFormat.units.HOUR]: intl.messages['react-intl-formatted-duration.hoursUnit'] || '{value, plural, one {hour} other {hours}}',
39 | [DurationUnitFormat.units.MINUTE]: intl.messages['react-intl-formatted-duration.minutesUnit'] || '{value, plural, one {minute} other {minutes}}',
40 | [DurationUnitFormat.units.SECOND]: intl.messages['react-intl-formatted-duration.secondsUnit'] || '{value, plural, one {second} other {seconds}}',
41 | },
42 | formatDuration: intl.messages['react-intl-formatted-duration.duration'] || '{value} {unit}',
43 | round: true, // TODO backward compatible, add a prop to configure it
44 | style: actualSytle,
45 | }).formatToParts(seconds);
46 |
47 | const Text = textComponent || intl.textComponent;
48 | const Value = valueComponent || textComponent || intl.textComponent;
49 | return React.createElement(Text, otherProps, parts.map((token) => {
50 | if (token.type === 'literal' || token.type === 'unit') return token.value;
51 | return React.createElement(Value, { key: token.type }, token.value);
52 | }));
53 | }
54 |
55 | export default injectIntl(DurationMessage);
56 |
--------------------------------------------------------------------------------
/index.stories.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import FormattedDuration, {TIMER_FORMAT} from './index';
3 |
4 | export default { title: 'Formatted Duration' };
5 |
6 | function withInput(defaultValue, Example) {
7 | return () => {
8 | const [seconds, setSeconds] = useState(defaultValue);
9 | return (
10 |
15 | );
16 | }}
17 |
18 | export const defaultFormat = withInput(95, ({seconds}) => (
19 |
20 | ));
21 |
22 | export const customFormats = withInput(180000, ({seconds}) => (
23 |
24 |
25 | Format: {`{days} {hours} {minutes} {seconds}`}
26 |
27 |
28 |
29 |
30 | Format: {`{hours} {minutes} {seconds}`}
31 |
32 |
33 |
34 |
35 | Format: {`{minutes} {seconds}`}
36 |
37 |
38 |
39 |
40 | Format: {`{seconds}`}
41 |
42 |
43 |
44 |
45 | ));
46 |
47 | export const styles = withInput(3610, ({seconds}) => (
48 |
49 |
50 | Unit Display: long
51 |
52 |
53 |
54 |
55 | Unit Display: short
56 |
57 |
58 |
59 |
60 | Unit Display: narrow
61 |
62 |
63 |
64 |
65 | TIMER
66 |
67 |
68 |
69 |
70 | ));
71 |
--------------------------------------------------------------------------------
/internals/testing/test-setup.js:
--------------------------------------------------------------------------------
1 | // setup file
2 | import { configure } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 |
5 | configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DurationMessage Messages
3 | *
4 | * This contains all the text for the DurationMessage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | duration: {
10 | id: 'react-intl-formatted-duration.duration',
11 | defaultMessage: '{value} {unit}',
12 | },
13 | longFormatting: {
14 | id: 'react-intl-formatted-duration.longFormatting',
15 | defaultMessage: '{minutes} {seconds}',
16 | },
17 | timerFormatting: {
18 | id: 'react-intl-formatted-duration.timerFormatting',
19 | defaultMessage: '{minutes}:{seconds}',
20 | },
21 | daysUnit: {
22 | id: 'react-intl-formatted-duration.daysUnit',
23 | defaultMessage: '{value, plural, one {day} other {days}}',
24 | },
25 | hoursUnit: {
26 | id: 'react-intl-formatted-duration.hoursUnit',
27 | defaultMessage: '{value, plural, one {hour} other {hours}}',
28 | },
29 | minutesUnit: {
30 | id: 'react-intl-formatted-duration.minutesUnit',
31 | defaultMessage: '{value, plural, one {minute} other {minutes}}',
32 | },
33 | secondsUnit: {
34 | id: 'react-intl-formatted-duration.secondsUnit',
35 | defaultMessage: '{value, plural, one {second} other {seconds}}',
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-intl-formatted-duration",
3 | "version": "4.0.0",
4 | "description": "React intl component to express time duration",
5 | "keywords": [
6 | "duration",
7 | "react",
8 | "intl",
9 | "format"
10 | ],
11 | "homepage": "https://github.com/en-japan-air/react-intl-formatted-duration",
12 | "license": "MIT",
13 | "main": "dist/bundle.js",
14 | "module": "dist/module.js",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/en-japan-air/react-intl-formatted-duration"
18 | },
19 | "files": [
20 | "dist",
21 | "index.js",
22 | "messages.js"
23 | ],
24 | "scripts": {
25 | "compile": "rollup -c rollup.config.js",
26 | "codecov": "codecov",
27 | "lint": "eslint index.js __tests__",
28 | "storybook": "start-storybook",
29 | "test": "jest",
30 | "test:coverage": "jest --coverage",
31 | "test:watch": "jest --watchAll",
32 | "prepare": "npm run compile",
33 | "posttest": "npm run lint"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.9.0",
37 | "@babel/preset-env": "^7.9.5",
38 | "@babel/preset-react": "^7.9.4",
39 | "@storybook/addons": "5.3.18",
40 | "@storybook/react": "5.3.18",
41 | "babel-core": "^7.0.0-bridge.0",
42 | "babel-eslint": "^10.1.0",
43 | "babel-jest": "^25.3.0",
44 | "babel-loader": "8.1.0",
45 | "codecov": "3.7.1",
46 | "enzyme": "3.11.0",
47 | "enzyme-adapter-react-16": "1.15.2",
48 | "eslint": "^6.8.0",
49 | "eslint-plugin-react": "^7.19.0",
50 | "jest": "^25.3.0",
51 | "react": "16.13.1",
52 | "react-dom": "16.13.1",
53 | "react-intl": "4.3.1",
54 | "react-test-renderer": "16.13.1",
55 | "rollup": "2.6.0",
56 | "rollup-plugin-babel": "4.4.0",
57 | "rollup-plugin-commonjs": "10.1.0",
58 | "rollup-plugin-filesize": "7.0.0",
59 | "rollup-plugin-node-resolve": "5.2.0",
60 | "storybook-addon-intl": "2.4.1"
61 | },
62 | "peerDependencies": {
63 | "react": ">= 16.0",
64 | "react-intl": ">= 4.0",
65 | "intl-messageformat": ">= 2.0"
66 | },
67 | "jest": {
68 | "setupFilesAfterEnv": [
69 | "/internals/testing/test-setup.js"
70 | ],
71 | "testRegex": "__tests__/.*\\.test\\.js$",
72 | "transform": {
73 | ".*": "/node_modules/babel-jest"
74 | }
75 | },
76 | "dependencies": {
77 | "intl-unofficial-duration-unit-format": "3.0.0"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import pkg from './package.json';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import resolve from 'rollup-plugin-node-resolve';
5 | import filesize from 'rollup-plugin-filesize';
6 |
7 | export default {
8 | input: 'index.js',
9 | plugins: [
10 | resolve({ module: false }),
11 | commonjs(),
12 | babel(),
13 | filesize(),
14 | ],
15 | external: Object.keys(pkg.peerDependencies),
16 | output: [{
17 | exports: 'named',
18 | file: 'dist/bundle.js',
19 | format: 'cjs',
20 | }, {
21 | file: 'dist/module.js',
22 | format: 'es',
23 | }],
24 | };
25 |
--------------------------------------------------------------------------------