diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 58d94001..7f71c10e 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -52,6 +52,7 @@ - [Format Bytes](/components/format-bytes.md) - [Format Number](/components/format-number.md) - [Include](/components/include.md) + - [Relative Time](/components/relative-time.md) - [Resize Observer](/components/resize-observer.md) - [Theme](/components/theme.md) diff --git a/docs/components/relative-time.md b/docs/components/relative-time.md new file mode 100644 index 00000000..5561b70c --- /dev/null +++ b/docs/components/relative-time.md @@ -0,0 +1,61 @@ +# Relative Time + +[component-header:sl-relative-time] + +Outputs a localized time phrase relative to the current time. + +Localization is handled by the browser's [Intl.RelativeTimeFormat API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) so there'e no need to load language packs. + +```html preview +<sl-relative-time date="2011-11-11T16:56:20-04:00"></sl-relative-time><br> +``` + +The `date` prop must be a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object or a string that [`Date.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) can interpret. When using strings, avoid ambiguous dates such as `03/04/2020` which can be interpreted as March 4 or April 3 depending on the user's browser and locale. Instead, use a valid [ISO 8601 date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format) to ensure proper parsing. + +?> [The Intl.RelativeTimeFormat API is available in all major browsers](https://caniuse.com/mdn-javascript_builtins_intl_relativetimeformat), but it first became available to Safari in version 14. If you need to support Safari 13, you'll need to [use a polyfill](https://github.com/catamphetamine/relative-time-format). + +## Examples + +### Keeping Time in Sync + +Use the `sync` attribute to update the displayed value as time passes. + +```html preview +<div class="relative-time-sync"> + <sl-relative-time sync></sl-relative-time> + <br><br> + <sl-button>Reset</sl-button> +</div> + +<script> + const container = document.querySelector('.relative-time-sync'); + const button = container.querySelector('sl-button'); + const relativeTime = container.querySelector('sl-relative-time'); + + relativeTime.date = new Date(); + button.addEventListener('click', () => relativeTime.date = new Date()); +</script> +``` + +### Formatting Styles + +You can change the way times are formatted with the `format` attribute. Note that some locales may show the same result for `narrow` and `short` formats. + +```html preview +<sl-relative-time date="2020-07-15T09:17:00" format="narrow"></sl-relative-time><br> +<sl-relative-time date="2020-07-15T09:17:00" format="short"></sl-relative-time><br> +<sl-relative-time date="2020-07-15T09:17:00" format="long"></sl-relative-time> +``` + +### Localization + +Use the `locale` attribute to set the desired locale. + +```html preview +English: <sl-relative-time date="2020-07-15T09:17:00" locale="en-US"></sl-relative-time><br> +Chinese: <sl-relative-time date="2020-07-15T09:17:00" locale="zh-CN"></sl-relative-time><br> +German: <sl-relative-time date="2020-07-15T09:17:00" locale="de-DE"></sl-relative-time><br> +Russian: <sl-relative-time date="2020-07-15T09:17:00" locale="ru-RU"></sl-relative-time> +``` + +[component-metadata:sl-relative-time] diff --git a/docs/getting-started/changelog.md b/docs/getting-started/changelog.md index 664cea8c..02ab789d 100644 --- a/docs/getting-started/changelog.md +++ b/docs/getting-started/changelog.md @@ -9,6 +9,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis ## Next - Added `sl-format-number` component +- Added `sl-relative-time` component - Added `closable` prop to `sl-tab` - Added experimental `sl-resize-observer` utility - Added experimental `sl-theme` utility and updated theming documentation diff --git a/src/components.d.ts b/src/components.d.ts index eaf719dd..8711be7d 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -897,6 +897,24 @@ export namespace Components { */ "value": number; } + interface SlRelativeTime { + /** + * The date from which to calculate time from. + */ + "date": Date | string; + /** + * The formatting style to use. + */ + "format": 'long' | 'short' | 'narrow'; + /** + * The locale to use when formatting the number. + */ + "locale": string; + /** + * When `auto`, values such as "yesterday" and "tomorrow" will be shown when possible. When `always`, values such as "1 day ago" and "in 1 day" will be shown. + */ + "numeric": 'always' | 'auto'; + } interface SlResizeObserver { } interface SlResponsiveEmbed { @@ -1434,6 +1452,12 @@ declare global { prototype: HTMLSlRatingElement; new (): HTMLSlRatingElement; }; + interface HTMLSlRelativeTimeElement extends Components.SlRelativeTime, HTMLStencilElement { + } + var HTMLSlRelativeTimeElement: { + prototype: HTMLSlRelativeTimeElement; + new (): HTMLSlRelativeTimeElement; + }; interface HTMLSlResizeObserverElement extends Components.SlResizeObserver, HTMLStencilElement { } var HTMLSlResizeObserverElement: { @@ -1544,6 +1568,7 @@ declare global { "sl-radio": HTMLSlRadioElement; "sl-range": HTMLSlRangeElement; "sl-rating": HTMLSlRatingElement; + "sl-relative-time": HTMLSlRelativeTimeElement; "sl-resize-observer": HTMLSlResizeObserverElement; "sl-responsive-embed": HTMLSlResponsiveEmbedElement; "sl-select": HTMLSlSelectElement; @@ -2477,6 +2502,24 @@ declare namespace LocalJSX { */ "value"?: number; } + interface SlRelativeTime { + /** + * The date from which to calculate time from. + */ + "date"?: Date | string; + /** + * The formatting style to use. + */ + "format"?: 'long' | 'short' | 'narrow'; + /** + * The locale to use when formatting the number. + */ + "locale"?: string; + /** + * When `auto`, values such as "yesterday" and "tomorrow" will be shown when possible. When `always`, values such as "1 day ago" and "in 1 day" will be shown. + */ + "numeric"?: 'always' | 'auto'; + } interface SlResizeObserver { /** * Emitted when the element is resized. @@ -2862,6 +2905,7 @@ declare namespace LocalJSX { "sl-radio": SlRadio; "sl-range": SlRange; "sl-rating": SlRating; + "sl-relative-time": SlRelativeTime; "sl-resize-observer": SlResizeObserver; "sl-responsive-embed": SlResponsiveEmbed; "sl-select": SlSelect; @@ -2912,6 +2956,7 @@ declare module "@stencil/core" { "sl-radio": LocalJSX.SlRadio & JSXBase.HTMLAttributes<HTMLSlRadioElement>; "sl-range": LocalJSX.SlRange & JSXBase.HTMLAttributes<HTMLSlRangeElement>; "sl-rating": LocalJSX.SlRating & JSXBase.HTMLAttributes<HTMLSlRatingElement>; + "sl-relative-time": LocalJSX.SlRelativeTime & JSXBase.HTMLAttributes<HTMLSlRelativeTimeElement>; "sl-resize-observer": LocalJSX.SlResizeObserver & JSXBase.HTMLAttributes<HTMLSlResizeObserverElement>; "sl-responsive-embed": LocalJSX.SlResponsiveEmbed & JSXBase.HTMLAttributes<HTMLSlResponsiveEmbedElement>; "sl-select": LocalJSX.SlSelect & JSXBase.HTMLAttributes<HTMLSlSelectElement>; diff --git a/src/components/relative-time/relative-time.tsx b/src/components/relative-time/relative-time.tsx new file mode 100644 index 00000000..336105b2 --- /dev/null +++ b/src/components/relative-time/relative-time.tsx @@ -0,0 +1,45 @@ +import { Component, Prop, State, h } from '@stencil/core'; + +/** + * @since 2.0 + * @status stable + */ + +@Component({ + tag: 'sl-relative-time', + shadow: true +}) +export class RelativeTime { + @State() displayTime = ''; + + /** The date from which to calculate time from. */ + @Prop() date: Date | string; + + /** The locale to use when formatting the number. */ + @Prop() locale: string; + + /** The formatting style to use. */ + @Prop() format: 'long' | 'short' | 'narrow' = 'long'; + + /** + * When `auto`, values such as "yesterday" and "tomorrow" will be shown when possible. When `always`, values such as + * "1 day ago" and "in 1 day" will be shown. + */ + @Prop() numeric: 'always' | 'auto' = 'auto'; + + render() { + const date = new Date(this.date); + + if (isNaN(date.getSeconds())) { + return ''; + } + + // // @ts-ignore - https://caniuse.com/mdn-javascript_builtins_intl_relativetimeformat + // new Intl.RelativeTimeFormat(this.locale, { + // numeric: this.numeric, + // style: this.type + // }).format(this.value, this.unit); + + return <time dateTime={date.toISOString()}>{this.displayTime}</time>; + } +}