Skip to content

Commit

Permalink
feat: add support for formatToParts
Browse files Browse the repository at this point in the history
  • Loading branch information
longlho committed Aug 30, 2019
1 parent 0b8f989 commit e8167f3
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 292 deletions.
24 changes: 21 additions & 3 deletions docs/Components.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,17 @@ props: Intl.DateTimeFormatOptions &
{
value: any,
format: string,
children: (formattedDate: string) => ReactElement,
children: (formattedDate: string | Intl.DateTimeFormatPart[]) =>
ReactElement,
shouldFormatToParts: boolean,
};
```

By default `<FormattedDate>` will render the formatted date into a `<React.Fragment>`. If you need to customize rendering, you can either wrap it with another React element (recommended), or pass a function as the child.

**Example:**

```js
```tsx
<FormattedDate value={new Date(1459832991883)} />
```

Expand All @@ -179,12 +181,28 @@ By default `<FormattedDate>` will render the formatted date into a `<React.Fragm

**Example with Options:**

```js
```tsx
<FormattedDate
value={new Date(1459832991883)}
year="numeric"
month="long"
day="2-digit"
/>
```

```html
<span>April 05, 2016</span>
```

**shouldFormatToParts**

```tsx
<FormattedDate
value={new Date(1459832991883)}
year="numeric"
month="long"
day="2-digit"
shouldFormatToParts={true}
/>
```

Expand Down
4 changes: 4 additions & 0 deletions examples/TimeZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const App: React.FC<Props> = ({currentTime = Date.now()}) => {
The date in Tokyo is: <FormattedDate value={currentTime} />
<br />
The time in Tokyo is: <FormattedTime value={currentTime} />
<br />
<FormattedDate value={currentTime} shouldFormatToParts>
{parts => <>{JSON.stringify(parts)}</>}
</FormattedDate>
</p>
</IntlProvider>
);
Expand Down
34 changes: 33 additions & 1 deletion src/components/createFormattedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import * as React from 'react';
import {invariantIntlContext} from '../utils';
import {IntlShape, FormatDateOptions, FormatNumberOptions} from '../types';
import {Context} from './injectIntl';
import {formatDateFactory, formatTimeFactory} from '../formatters/dateTime';
import {formatNumberFactory} from '../formatters/number';
// Since rollup cannot deal with namespace being a function,
// this is to interop with TypeScript since `invariant`
// does not export a default
// https://github.com/rollup/rollup/issues/1267
import * as invariant_ from 'invariant';
const invariant: typeof invariant_ = (invariant_ as any).default || invariant_;

enum DisplayName {
formatDate = 'FormattedDate',
Expand All @@ -21,6 +29,7 @@ export default function createFormattedComponent<Name extends keyof Formatter>(
type Options = Formatter[Name];
type FormatFn = IntlShape[Name];
type Props = Options & {
shouldFormatToParts?: boolean;
value: Parameters<FormatFn>[0];
children?: (val: string) => React.ReactElement | null;
};
Expand All @@ -29,7 +38,30 @@ export default function createFormattedComponent<Name extends keyof Formatter>(
<Context.Consumer>
{intl => {
invariantIntlContext(intl);

let formattedParts;
if (props.shouldFormatToParts) {
if (name === 'formatDate') {
formattedParts = formatDateFactory(
intl,
intl.formatters.getDateTimeFormat
)(props.value, props);
} else if (name === 'formatTime') {
formattedParts = formatTimeFactory(
intl,
intl.formatters.getDateTimeFormat
)(props.value, props);
} else {
formattedParts = formatNumberFactory(
intl,
intl.formatters.getNumberFormat
)(props.value as number, props);
}
invariant(
typeof props.children === 'function',
'render props must be a function when `shouldFormatToParts` is `true`'
);
return props.children!(formattedParts);
}
const formattedValue = intl[name](props.value as any, props);

if (typeof props.children === 'function') {
Expand Down
4 changes: 2 additions & 2 deletions src/components/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as React from 'react';
import {PrimitiveType, FormatXMLElementFn} from 'intl-messageformat';
import {Context} from './injectIntl';
import {MessageDescriptor} from '../types';
import {formatMessage as baseFormatMessage} from '../format';
import {formatMessage} from '../formatters/message';
import {
invariantIntlContext,
DEFAULT_INTL_CONFIG,
Expand All @@ -31,7 +31,7 @@ const defaultFormatMessage = (
);
}

return baseFormatMessage(
return formatMessage(
{
...DEFAULT_INTL_CONFIG,
locale: 'en',
Expand Down
37 changes: 17 additions & 20 deletions src/components/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,12 @@ import {
createIntlCache,
} from '../utils';
import {IntlConfig, IntlShape, Omit, IntlCache} from '../types';
import {
formatNumber,
formatRelativeTime,
formatDate,
formatTime,
formatPlural,
formatHTMLMessage,
formatMessage,
} from '../format';
import areIntlLocalesSupported from 'intl-locales-supported';
import {formatNumberFactory} from '../formatters/number';
import {formatRelativeTimeFactory} from '../formatters/relativeTime';
import {formatDateFactory, formatTimeFactory} from '../formatters/dateTime';
import {formatPluralFactory} from '../formatters/plural';
import {formatMessage, formatHTMLMessage} from '../formatters/message';
import * as shallowEquals_ from 'shallow-equal/objects';
const shallowEquals: typeof shallowEquals_ =
(shallowEquals_ as any).default || shallowEquals_;
Expand Down Expand Up @@ -134,20 +130,21 @@ export function createIntl(
return {
...resolvedConfig,
formatters,
formatNumber: formatNumber.bind(undefined, resolvedConfig, formatters),
formatRelativeTime: formatRelativeTime.bind(
undefined,
formatNumber: formatNumberFactory(
resolvedConfig,
formatters.getNumberFormat
),
formatRelativeTime: formatRelativeTimeFactory(
resolvedConfig,
formatters
formatters.getRelativeTimeFormat
),
formatDate: formatDate.bind(undefined, resolvedConfig, formatters),
formatTime: formatTime.bind(undefined, resolvedConfig, formatters),
formatPlural: formatPlural.bind(undefined, resolvedConfig, formatters),
formatMessage: formatMessage.bind(undefined, resolvedConfig, formatters),
formatHTMLMessage: formatHTMLMessage.bind(
undefined,
formatDate: formatDateFactory(resolvedConfig, formatters.getDateTimeFormat),
formatTime: formatTimeFactory(resolvedConfig, formatters.getDateTimeFormat),
formatPlural: formatPluralFactory(
resolvedConfig,
formatters
formatters.getPluralRules
),
formatMessage: formatMessage.bind(null, resolvedConfig, formatters),
formatHTMLMessage: formatHTMLMessage.bind(null, resolvedConfig, formatters),
};
}
106 changes: 106 additions & 0 deletions src/formatters/dateTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2015, Yahoo Inc.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/

import {Formatters, IntlConfig, IntlFormatters} from '../types';

import {createError, filterProps, getNamedFormat} from '../utils';

const DATE_TIME_FORMAT_OPTIONS: Array<keyof Intl.DateTimeFormatOptions> = [
'localeMatcher',
'formatMatcher',

'timeZone',
'hour12',

'weekday',
'era',
'year',
'month',
'day',
'hour',
'minute',
'second',
'timeZoneName',
];

function getFormatter(
{
locale,
formats,
onError,
timeZone,
}: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
type: 'date' | 'time',
getDateTimeFormat: Formatters['getDateTimeFormat'],
options: Parameters<IntlFormatters['formatDate']>[1] = {}
) {
const {format} = options;
let defaults = {
...(timeZone && {timeZone}),
...(format && getNamedFormat(formats!, type, format, onError)),
};

let filteredOptions = filterProps(
options,
DATE_TIME_FORMAT_OPTIONS,
defaults
);

if (
type === 'time' &&
!filteredOptions.hour &&
!filteredOptions.minute &&
!filteredOptions.second
) {
// Add default formatting options if hour, minute, or second isn't defined.
filteredOptions = {...filteredOptions, hour: 'numeric', minute: 'numeric'};
}

return getDateTimeFormat(locale, filteredOptions);
}

export function formatDateFactory(
config: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
getDateTimeFormat: Formatters['getDateTimeFormat']
) {
return (
value?: Parameters<IntlFormatters['formatDate']>[0],
options: Parameters<IntlFormatters['formatDate']>[1] = {}
) => {
const date = typeof value === 'string' ? new Date(value || 0) : value;
try {
return getFormatter(config, 'date', getDateTimeFormat, options).format(
date
);
} catch (e) {
config.onError(createError('Error formatting date.', e));
}

return String(date);
};
}

export function formatTimeFactory(
config: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
getDateTimeFormat: Formatters['getDateTimeFormat']
) {
return (
value?: Parameters<IntlFormatters['formatTime']>[0],
options: Parameters<IntlFormatters['formatTime']>[1] = {}
) => {
const date = typeof value === 'string' ? new Date(value || 0) : value;

try {
return getFormatter(config, 'time', getDateTimeFormat, options).format(
date
);
} catch (e) {
config.onError(createError('Error formatting time.', e));
}

return String(date);
};
}
Loading

0 comments on commit e8167f3

Please sign in to comment.