diff --git a/.changeset/lucky-gifts-tie.md b/.changeset/lucky-gifts-tie.md new file mode 100644 index 0000000000..126cfe2211 --- /dev/null +++ b/.changeset/lucky-gifts-tie.md @@ -0,0 +1,34 @@ +--- +"@patternfly/elements": major +--- + +Added `` replacing ``. Timestamp now follows +PatternFly v6 design specs. + +```html + + +``` + +**Breaking Changes from v5** + +- `` renamed to `` +- `utc` boolean attribute replaced with `time-zone` string attribute accepting + any IANA timezone identifier (e.g. `time-zone="UTC"`, + `time-zone="America/New_York"`) +- `hour-12` boolean attribute replaced with `hour-cycle` enum attribute + accepting Intl values: `h11`, `h12`, `h23`, `h24` +- `help-text` attribute removed; tooltip styling is a composition pattern + using `` wrapping +- `date` getter now returns ISO 8601 string instead of locale-formatted string +- `display-suffix` no longer auto-set to "UTC" when using UTC timezone; set + `display-suffix="UTC"` explicitly if needed + +**New features** + +- `time-zone` attribute for any IANA timezone, not just UTC +- `hour-cycle` attribute for precise hour format control +- Default slot for custom display content +- v6 design tokens diff --git a/core/pfe-core/controllers/timestamp-controller.ts b/core/pfe-core/controllers/timestamp-controller.ts index e6deb68b49..53d4be5c81 100644 --- a/core/pfe-core/controllers/timestamp-controller.ts +++ b/core/pfe-core/controllers/timestamp-controller.ts @@ -2,49 +2,49 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit'; export type DateTimeFormat = 'full' | 'long' | 'medium' | 'short'; +export type HourCycle = 'h11' | 'h12' | 'h23' | 'h24'; + export interface TimestampOptions { dateFormat?: DateTimeFormat; timeFormat?: DateTimeFormat; customFormat?: Intl.DateTimeFormatOptions; - displaySuffix: string; - locale: Intl.LocalesArgument; - relative: boolean; - utc: boolean; - hour12: boolean; + displaySuffix?: string; + locale?: Intl.LocalesArgument; + relative?: boolean; + timeZone?: string; + hourCycle?: HourCycle; } -const defaults = { - dateFormat: undefined, - timeFormat: undefined, - customFormat: undefined, - displaySuffix: '', - locale: undefined, - relative: false, - utc: false, - hour12: false, -} as const; +const optionKeys: Record = { + dateFormat: true, + timeFormat: true, + customFormat: true, + displaySuffix: true, + locale: true, + relative: true, + timeZone: true, + hourCycle: true, +}; export class TimestampController implements ReactiveController { static #isTimestampOptionKey(prop: PropertyKey): prop is keyof TimestampOptions { - return prop in defaults; + return prop in optionKeys; } + // When Temporal reaches baseline, replace with Temporal.Instant; + // timeZone and hourCycle options already align with Temporal's API #date = new Date(); - #options: TimestampOptions = {} as TimestampOptions; + #options: Partial = {}; #host: ReactiveControllerHost; - get localeString(): string { - return this.#date.toLocaleString(this.#options.locale); - } - get date(): Date { return this.#date; } - set date(string) { - this.#date = new Date(string); + set date(value: string | Date) { + this.#date = new Date(value); } get isoString(): string { @@ -54,38 +54,29 @@ export class TimestampController implements ReactiveController { get time(): string { if (this.#options.relative) { return this.#getTimeRelative(); - } else { - let { displaySuffix } = this.#options; - const { locale } = this.#options; - if (this.#options.utc) { - displaySuffix ||= 'UTC'; - } - const localeString = this.#date.toLocaleString(locale, this.#options.customFormat ?? { - hour12: this.#options.hour12, - timeStyle: this.#options.timeFormat, - dateStyle: this.#options.dateFormat, - ...this.#options.utc && { timeZone: 'UTC' }, - }); - - return `${localeString} ${displaySuffix ?? ''}`.trim(); } + const { displaySuffix, locale, timeZone, hourCycle } = this.#options; + const localeString = this.#date.toLocaleString(locale, this.#options.customFormat ?? { + hourCycle, + timeStyle: this.#options.timeFormat, + dateStyle: this.#options.dateFormat, + timeZone, + }); + return `${localeString}${displaySuffix ? ` ${displaySuffix}` : ''}`; } constructor(host: ReactiveControllerHost, options?: Partial) { this.#host = host; host.addController(this); - for (const [name, value] of Object.entries(this.#options)) { - // @ts-expect-error: seems typescript compiler isn't up to the task here - this.#options[name] = options?.[name] ?? value; + if (options) { + Object.assign(this.#options, options); } } hostConnected?(): void; - /** - * Based off of Github Relative Time - * https://github.com/github/time-elements/blob/master/src/relative-time.js - */ + // When Temporal reaches baseline, replace Intl.RelativeTimeFormat usage + // with Temporal.Duration and Temporal.Now.instant() for precise unit selection #getTimeRelative() { const date = this.#date; const { locale } = this.#options; @@ -129,7 +120,7 @@ export class TimestampController implements ReactiveController { set(prop: PropertyKey, value: unknown): void { if (TimestampController.#isTimestampOptionKey(prop)) { - // @ts-expect-error: seems typescript compiler isn't up to the task here + // @ts-expect-error: dynamic property assignment from element willUpdate this.#options[prop] = value; this.#host.requestUpdate(); } diff --git a/docs/main.mjs b/docs/main.mjs index 2664df6bb8..cb235fa5f9 100644 --- a/docs/main.mjs +++ b/docs/main.mjs @@ -32,7 +32,7 @@ import '@patternfly/elements/pf-v5-tabs/pf-v5-tabs.js'; import '@patternfly/elements/pf-v5-text-area/pf-v5-text-area.js'; import '@patternfly/elements/pf-v5-text-input/pf-v5-text-input.js'; import '@patternfly/elements/pf-v5-tile/pf-v5-tile.js'; -import '@patternfly/elements/pf-v5-timestamp/pf-v5-timestamp.js'; +import '@patternfly/elements/pf-v6-timestamp/pf-v6-timestamp.js'; import '@patternfly/elements/pf-v5-tooltip/pf-v5-tooltip.js'; // if `/v2/` path load icons from static directory diff --git a/elements/pf-v5-timestamp/README.md b/elements/pf-v5-timestamp/README.md deleted file mode 100644 index 1e39721e09..0000000000 --- a/elements/pf-v5-timestamp/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# PatternFly Elements Timestamp - -A timestamp provides consistent formats for displaying date and time values. - -Read more about Datetime in the [PatternFly Elements Timestamp documentation](https://patternflyelements.org/components/timestamp) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-v5-timestamp/pf-v5-timestamp.js'; -``` - -## Usage - -### Just the date: January 2, 2006 -```html - - -``` - -### With time: Monday, January 2, 2006 at 3:04:05 PM EST -```html - - -``` - -### With an en-GB locale: Monday, 2 January 2006 at 15:04:05 GMT-5 -You can use any locale here. -```html - - -``` - -### Relative time: 17 years ago -```html - - -``` - diff --git a/elements/pf-v5-timestamp/demo/basic-formats.html b/elements/pf-v5-timestamp/demo/basic-formats.html deleted file mode 100644 index 8486314b15..0000000000 --- a/elements/pf-v5-timestamp/demo/basic-formats.html +++ /dev/null @@ -1,17 +0,0 @@ -
-

-

-

-

-

-
- - - - diff --git a/elements/pf-v5-timestamp/demo/custom-format.html b/elements/pf-v5-timestamp/demo/custom-format.html deleted file mode 100644 index 094a64fc00..0000000000 --- a/elements/pf-v5-timestamp/demo/custom-format.html +++ /dev/null @@ -1,23 +0,0 @@ -
-

- - -
- - - - diff --git a/elements/pf-v5-timestamp/demo/custom-tooltip.html b/elements/pf-v5-timestamp/demo/custom-tooltip.html deleted file mode 100644 index 8cb5b91417..0000000000 --- a/elements/pf-v5-timestamp/demo/custom-tooltip.html +++ /dev/null @@ -1,25 +0,0 @@ -
-

- - - Last updated on - -

-

- - Halloween - - -

-
- - - - diff --git a/elements/pf-v5-timestamp/demo/index.html b/elements/pf-v5-timestamp/demo/index.html deleted file mode 100644 index 1b50e9d610..0000000000 --- a/elements/pf-v5-timestamp/demo/index.html +++ /dev/null @@ -1,13 +0,0 @@ -
-

-
- - - - diff --git a/elements/pf-v5-timestamp/demo/relative-format-with-tooltip.html b/elements/pf-v5-timestamp/demo/relative-format-with-tooltip.html deleted file mode 100644 index a74706684f..0000000000 --- a/elements/pf-v5-timestamp/demo/relative-format-with-tooltip.html +++ /dev/null @@ -1,25 +0,0 @@ -
-

- - - - -

-

- - - - -

-
- - - - diff --git a/elements/pf-v5-timestamp/demo/relative-format.html b/elements/pf-v5-timestamp/demo/relative-format.html deleted file mode 100644 index 9f6e90d2d6..0000000000 --- a/elements/pf-v5-timestamp/demo/relative-format.html +++ /dev/null @@ -1,18 +0,0 @@ -
-

-

-

-

-

-

-
- - - - diff --git a/elements/pf-v5-timestamp/demo/tooltip.html b/elements/pf-v5-timestamp/demo/tooltip.html deleted file mode 100644 index 4f692c36aa..0000000000 --- a/elements/pf-v5-timestamp/demo/tooltip.html +++ /dev/null @@ -1,25 +0,0 @@ -
-

- - - - -

-

- - - - -

-
- - - - diff --git a/elements/pf-v5-timestamp/docs/CHANGELOG.old.md b/elements/pf-v5-timestamp/docs/CHANGELOG.old.md deleted file mode 100644 index 468dd61ee6..0000000000 --- a/elements/pf-v5-timestamp/docs/CHANGELOG.old.md +++ /dev/null @@ -1,57 +0,0 @@ -# @patternfly/pfe-timestamp - -## 2.0.0-next.5 - -### Minor Changes - -- daba8a53: Changing from pfe-datetime to pfe-timestamp - -## 2.0.0-next.4 - -### Patch Changes - -- bfad8b4b: Updates dependencies -- Updated dependencies [bfad8b4b] - - @patternfly/pfe-core@2.0.0-next.8 - -## 2.0.0-next.3 - -### Patch Changes - -- 6a2a0407: [View commit message here](https://gist.github.com/heyMP/200fc0b840690541475923facba393ab) -- Updated dependencies [6a2a0407] - - @patternfly/pfe-core@2.0.0-next.4 - -## 2.0.0-next.2 - -### Patch Changes - -- 447b2d75: Remove `esbuild` export condition, as this anyways was a runtime error -- Updated dependencies [447b2d75] - - @patternfly/pfe-core@2.0.0-next.3 - -## 2.0.0-next.1 - -### Patch Changes - -- 1e89269b: document `time-zone-name` attribute - -## 2.0.0-next.0 - -### Major Changes - -- 48e48655: ## 🔥 Migrate to Lit - - This release migrates `` to LitElement. - - ### Breaking Changes - - - Initial render is now [asynchronous](https://lit.dev/docs/components/lifecycle/#reactive-update-cycle). - If your code assumes that shadow DOM is ready once the element is constructed, update it to `await element.updateComplete` - - See [docs](https://patternflyelements.org/components/datetime/) for more info - -### Patch Changes - -- Updated dependencies [e8788c72] - - @patternfly/pfe-core@2.0.0-next.0 diff --git a/elements/pf-v5-timestamp/docs/pf-v5-timestamp.md b/elements/pf-v5-timestamp/docs/pf-v5-timestamp.md deleted file mode 100644 index ea081ec503..0000000000 --- a/elements/pf-v5-timestamp/docs/pf-v5-timestamp.md +++ /dev/null @@ -1,139 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - A timestamp provides consistent formats for displaying date and time values. - - Default: - - With a locale of es: - - Relative time: -{% endrenderOverview %} - -{% band header="Usage" %} - ### Default - By default, a timestamp will display the current date and time based on the current locale if the date attribute is not set. - - {% htmlexample %} - - {% endhtmlexample %} - - ### Basic formats - The format of the displayed content can be customized by setting the `date-format` and/or `time-format` attributes. Setting only one of the attributes will display only the date or time, depending on which attribute is set. The possible options are "full", "long", "medium", and "short". - - You can also set the `display-suffix` attribute to display a custom suffix at the end of the displayed content. This will not override a timezone that is already displayed from the applied time format. - - {% htmlexample %} - - {% endhtmlexample %} - - {% htmlexample %} - - {% endhtmlexample %} - - {% htmlexample %} - - {% endhtmlexample %} - - {% htmlexample %} - - - {% endhtmlexample %} - - ### Custom format - The format of the displayed content can be further customized by setting the custom-format attributes. Read [datetime format options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options) for a list of options that can be set. - - {% htmlexample %} - - - {% endhtmlexample %} - - ### Adding a tooltip - To add a tooltip that displays the timestamp content as a UTC time, you can wrap `pf-v5-timestamp` with `pf-v5-tooltip` and set the `utc` attribute on an additional `pf-v5-timestamp`. - - {% htmlexample %} - - - - - {% endhtmlexample %} - - {% htmlexample %} - - - - - {% endhtmlexample %} - - ### Relative time - To display relative time, set the `relative` attribute on `pf-v5-timestamp`. - - {% htmlexample %} - - {% endhtmlexample %} - - {% htmlexample %} - - {% endhtmlexample %} - - ### Relative time with a tooltip - To display relative time, set the `relative` attribute on `pf-v5-timestamp`. - - {% htmlexample %} - - - - - {% endhtmlexample %} - - {% htmlexample %} - - - - - {% endhtmlexample %} - - ### Set a locale to something other than the default locale - The default locale is inferred by the browser. To set the locale to something else, set the `locale` attribute. - - {% htmlexample %} - - {% endhtmlexample %} - - {% htmlexample %} - - {% endhtmlexample %} - - ### As a UTC timestamp - Set the `utc` attribute. - - {% htmlexample %} - - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-timestamp/docs/screenshot.png b/elements/pf-v5-timestamp/docs/screenshot.png deleted file mode 100644 index 07767332a1..0000000000 Binary files a/elements/pf-v5-timestamp/docs/screenshot.png and /dev/null differ diff --git a/elements/pf-v5-timestamp/pf-v5-timestamp.css b/elements/pf-v5-timestamp/pf-v5-timestamp.css deleted file mode 100644 index 91def3703c..0000000000 --- a/elements/pf-v5-timestamp/pf-v5-timestamp.css +++ /dev/null @@ -1,8 +0,0 @@ -:host { - display: inline; -} - -time { - text-decoration: var(--_timestamp-text-decoration, none); - text-underline-offset: var(--_timestamp-text-underline-offset, initial); -} diff --git a/elements/pf-v5-timestamp/pf-v5-timestamp.ts b/elements/pf-v5-timestamp/pf-v5-timestamp.ts deleted file mode 100644 index 040e9fec6f..0000000000 --- a/elements/pf-v5-timestamp/pf-v5-timestamp.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { ComplexAttributeConverter, PropertyValues, TemplateResult } from 'lit'; - -import { LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { - TimestampController, - type DateTimeFormat, -} from '@patternfly/pfe-core/controllers/timestamp-controller.js'; - -import style from './pf-v5-timestamp.css'; - -const BooleanStringConverter: ComplexAttributeConverter = { - fromAttribute(value) { - return !value || value === 'true'; - }, -}; - -/** - * A **timestamp** provides consistent formats for displaying date and time values. - * @alias Timestamp - */ -@customElement('pf-v5-timestamp') -export class PfV5Timestamp extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - @property({ reflect: true, attribute: 'date-format' }) dateFormat?: DateTimeFormat; - - @property({ reflect: true, attribute: 'time-format' }) timeFormat?: DateTimeFormat; - - @property({ attribute: false }) customFormat?: object; - - @property({ reflect: true, attribute: 'display-suffix' }) displaySuffix?: string; - - @property({ reflect: true }) locale?: string; - - @property({ reflect: true, type: Boolean }) relative?: boolean; - - @property({ reflect: true, type: Boolean }) utc?: boolean; - - @property({ - reflect: true, - attribute: 'hour-12', - converter: BooleanStringConverter, - }) hour12?: boolean; - - @property({ reflect: true }) - get date(): string { - return this.#timestamp.localeString; - } - - set date(string) { - this.#timestamp.date = new Date(string); - } - - get isoString(): string { - return this.#timestamp.isoString; - } - - get time(): string { - return this.#timestamp.time; - } - - #timestamp = new TimestampController(this); - - connectedCallback(): void { - super.connectedCallback(); - if (this.hasAttribute('date')) { - this.#timestamp.date = new Date(this.getAttribute('date')!); - } - } - - willUpdate(changedProperties: PropertyValues): void { - for (const [prop] of changedProperties) { - this.#timestamp.set(prop, this[prop as keyof this]); - } - } - - render(): TemplateResult<1> { - return html` - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-v5-timestamp': PfV5Timestamp; - } -} diff --git a/elements/pf-v5-timestamp/test/pf-timestamp.spec.ts b/elements/pf-v5-timestamp/test/pf-timestamp.spec.ts deleted file mode 100644 index 6155fd4d09..0000000000 --- a/elements/pf-v5-timestamp/test/pf-timestamp.spec.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfV5Timestamp } from '@patternfly/elements/pf-v5-timestamp/pf-v5-timestamp.js'; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-v5-timestamp')).to.be.an.instanceof(PfV5Timestamp); - }); - - it('should upgrade', async function() { - const element = await createFixture(html``); - expect(element, 'the should be an instance of PfV5Timestamp') - .to.be.an.instanceof(customElements.get('pf-v5-timestamp')) - .and - .to.be.an.instanceof(PfV5Timestamp); - }); - - it('should show the current date by default with default formatting', async function() { - const element = await createFixture(html` - - `); - - const expected = new Date().toLocaleString(); - - expect(element.time).to.equal(expected); - }); - - it('should set the correct ISO date on the datetime attribute in the time element', async function() { - const date = new Date('Sat Jan 01 2022 00:00:00'); - const dateString = date.toString(); - const expected = date.toISOString(); - const element = await createFixture(html` - - `); - - expect(element.isoString).to.equal(expected); - }); - - it('should show a passed in date with default formatting', async function() { - const dateString = 'Sat Jan 01 2022 00:00:00'; - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(new Date(dateString).toLocaleString()); - }); - - it('should show custom formatting when date-format and time-format are passed in', async function() { - const dateString = 'Sat Jan 01 2022 00:00:00'; - const expected = new Date(dateString).toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' }); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show only a date when date-format is passed in', async function() { - const dateString = 'Sat Jan 01 2022 00:00:00'; - const expected = new Date(dateString).toLocaleString('en-US', { dateStyle: 'full' }); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show only time when time-format is passed in', async function() { - const dateString = 'Sat Jan 01 2022 00:00:00'; - const expected = new Date(dateString).toLocaleString('en-US', { timeStyle: 'short' }); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show custom formatting when customFormat is passed in', async function() { - const dateString = 'Sat Jan 01 2022 00:00:00'; - const options: Intl.DateTimeFormatOptions = { - year: '2-digit', - month: 'short', - weekday: 'short', - day: 'numeric', - hour: 'numeric', - }; - const expected = new Date(dateString).toLocaleString('en-US', options); - const element = await createFixture(html` - - `); - expect(element.time).to.equal(expected); - }); - - it('should show a custom suffix when display-suffix is passed in', async function() { - const dateString = 'Sat Jan 01 2022 00:00:00'; - const suffix = 'US Eastern'; - const expected = `${new Date(dateString).toLocaleString('en-US')} ${suffix}`; - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show a 12 hour time', async function() { - const dateString = 'Sat Jan 01 2022 13:00:00'; - const expected = new Date(dateString).toLocaleString('en-US'); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show a 24 hour time when hour-12 is set to false', async function() { - const dateString = 'Sat Jan 01 2022 13:00:00'; - const expected = new Date(dateString).toLocaleString('en-US', { hour12: false }); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show with locale passed in', async function() { - const date = new Date(2022, 1, 1).toString(); - const expected = new Date(date).toLocaleString('en-GB'); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show a 12 hour time by default for US locale', async function() { - const date = new Date(2022, 1, 1, 13, 0).toString(); - const expected = new Date(date).toLocaleString('en-US'); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show a 24 hour time for US locale when hour-12 is false', async function() { - const date = new Date(2022, 1, 1, 13, 0).toString(); - const expected = new Date(date).toLocaleString('en-US', { hour12: false }); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show a 12 hour time for a 24 hour locale when hour-12 is passed', async function() { - const date = new Date(2022, 1, 1, 13, 0).toString(); - const expected = new Date(date).toLocaleString('en-GB', { hour12: true }); - const element = await createFixture(html` - - `); - - expect(element.time).to.equal(expected); - }); - - it('should show relative time of the moment', async function() { - const date = new Date(); - const element = await createFixture(html` - - `); - - expect(element.time).to.match(/just now/); - }); - - it('should show relative time in the past', async function() { - const date = new Date(2015, 7, 9, 14, 57, 0); - const element = await createFixture(html` - - `); - - expect(element.time).to.match(/\d+ years ago/); - }); - - it('should show relative time in the future', async function() { - const date = new Date(2099, 7, 9, 14, 57, 0); - const element = await createFixture(html` - - `); - - expect(element.time).to.match(/in \d+ years/); - }); -}); diff --git a/elements/pf-v6-timestamp/README.md b/elements/pf-v6-timestamp/README.md new file mode 100644 index 0000000000..d95232f4df --- /dev/null +++ b/elements/pf-v6-timestamp/README.md @@ -0,0 +1,81 @@ +# pf-v6-timestamp + +A timestamp provides consistent formats for displaying date and time values. + +## Usage + +### Basic date and time + +```html + + +``` + +### Relative time + +```html + + +``` + +### Timezone display + +```html + + +``` + +### Hour cycle + +```html + + + + +``` + +### With tooltip (composition pattern) + +Tooltip support uses composition with `` rather than built-in +configuration. Use an anchor element around the trigger for keyboard +accessibility. + +```html + + + + + + + +``` + +## Divergences from React `Timestamp` + +### Not implemented + +| React prop | Notes | +|---|---| +| `tooltip` | Use composition with `` wrapping the timestamp instead. The web component pattern favors slot-based composition over configuration objects. See demos for examples. | + +### Changed API + +| React prop | Web component | Difference | +|---|---|---| +| `is12Hour={true}` | `hour-cycle="h12"` | Uses the Intl.DateTimeFormat `hourCycle` enum (`h11`, `h12`, `h23`, `h24`) instead of a boolean. More expressive: `h23` and `h24` give two 24-hour variants, `h11` and `h12` give two 12-hour variants. Absence uses locale default (same as omitting `is12Hour` in React). | +| `is12Hour={false}` | `hour-cycle="h23"` | `h23` is the standard 24-hour format (0-23 range). | +| `shouldDisplayUTC` | `time-zone="UTC"` | Accepts any IANA timezone identifier, not just UTC. For the "UTC" suffix shown in React's default tooltip, add `display-suffix="UTC"`. | +| `date` (Date object) | `date` attribute (string) | Accepts any string parseable by `new Date()` rather than a Date object, since HTML attributes are strings. The `date` getter returns the ISO 8601 string for round-tripping. | +| `customFormat` (prop) | `.customFormat` property | Set via JavaScript property only (not reflectable as attribute). | +| `tooltip.variant="default"` | Composition with `` | Tooltip via wrapping; keyboard a11y via `` element. | +| `children` | Default slot | Slotted content replaces computed display, matching web component conventions. | + +### Added + +| Web component API | Notes | +|---|---| +| `time-zone` attribute | Accepts any IANA timezone identifier (e.g. `UTC`, `America/New_York`, `Europe/London`). Strictly more powerful than React's boolean `shouldDisplayUTC`. | +| `hour-cycle` attribute | Accepts `h11`, `h12`, `h23`, `h24` per the Intl.DateTimeFormat spec. Strictly more expressive than React's boolean `is12Hour`. | +| Default slot | Slot for custom display content (e.g. relative time text). The `datetime` attribute on the inner `
+

+ + + + 1 hour ago + + + + +

+

+ + + + Last updated August 9th, 2022 at 2:57 PM EDT + + + + +

+
+ + diff --git a/elements/pf-v6-timestamp/demo/custom-format.html b/elements/pf-v6-timestamp/demo/custom-format.html new file mode 100644 index 0000000000..7f98ea845d --- /dev/null +++ b/elements/pf-v6-timestamp/demo/custom-format.html @@ -0,0 +1,24 @@ +--- +name: Custom format +description: > + The format can be further customized by setting the customFormat property to + an Intl.DateTimeFormatOptions object. This overrides date-format and + time-format. +--- +
+

+ + +
+ + diff --git a/elements/pf-v6-timestamp/demo/custom-tooltip.html b/elements/pf-v6-timestamp/demo/custom-tooltip.html new file mode 100644 index 0000000000..0fb827d40e --- /dev/null +++ b/elements/pf-v6-timestamp/demo/custom-tooltip.html @@ -0,0 +1,28 @@ +--- +name: Custom tooltip +description: > + Display any custom content within a tooltip by placing it in the tooltip's + content slot. Use an anchor element around the trigger for keyboard + accessibility. +--- +
+

+ + + + + Last updated on + +

+

+ + Halloween + + +

+
+ + diff --git a/elements/pf-v6-timestamp/demo/default-tooltip.html b/elements/pf-v6-timestamp/demo/default-tooltip.html new file mode 100644 index 0000000000..361c529ae2 --- /dev/null +++ b/elements/pf-v6-timestamp/demo/default-tooltip.html @@ -0,0 +1,31 @@ +--- +name: Default tooltip +description: > + To render a tooltip that displays the timestamp content as a UTC time, wrap + the timestamp in a tooltip and add a UTC-formatted timestamp in the content + slot. Use an anchor element around the trigger timestamp for keyboard + accessibility. +--- +
+

+ + + + + + +

+

+ + + + + + +

+
+ + diff --git a/elements/pf-v6-timestamp/demo/hour-cycle.html b/elements/pf-v6-timestamp/demo/hour-cycle.html new file mode 100644 index 0000000000..8806e017d4 --- /dev/null +++ b/elements/pf-v6-timestamp/demo/hour-cycle.html @@ -0,0 +1,20 @@ +--- +name: Hour cycle +description: > + Control hour display with the hour-cycle attribute. Values follow the + Intl.DateTimeFormat spec: h12 (12-hour, 1-12), h11 (12-hour, 0-11), + h23 (24-hour, 0-23), h24 (24-hour, 1-24). When absent, the locale + default is used. This replaces React's boolean is12Hour with a richer + set of options. +--- +
+

Locale default:

+

12-hour (h12):

+

24-hour (h23):

+

en-GB with h12:

+

en-US with h23:

+
+ + diff --git a/elements/pf-v6-timestamp/demo/index.html b/elements/pf-v6-timestamp/demo/index.html new file mode 100644 index 0000000000..5f11d171c6 --- /dev/null +++ b/elements/pf-v6-timestamp/demo/index.html @@ -0,0 +1,15 @@ +--- +name: Default +description: > + By default, a timestamp will display the current date and time based on the + current locale if the date attribute is not set. Setting time-zone displays + the date and time in the specified IANA timezone. +--- +
+

+

+
+ + diff --git a/elements/pf-v6-timestamp/demo/relative-format-with-tooltip.html b/elements/pf-v6-timestamp/demo/relative-format-with-tooltip.html new file mode 100644 index 0000000000..42ddb71194 --- /dev/null +++ b/elements/pf-v6-timestamp/demo/relative-format-with-tooltip.html @@ -0,0 +1,29 @@ +--- +name: Relative format with tooltip +description: > + Combine relative time with a tooltip showing the full date/time. Use an + anchor element around the trigger for keyboard accessibility. +--- +
+

+ + + + + + +

+

+ + + + + + +

+
+ + diff --git a/elements/pf-v6-timestamp/demo/relative-format.html b/elements/pf-v6-timestamp/demo/relative-format.html new file mode 100644 index 0000000000..2560480ddb --- /dev/null +++ b/elements/pf-v6-timestamp/demo/relative-format.html @@ -0,0 +1,17 @@ +--- +name: Relative format +description: > + To display relative time (e.g. "3 years ago"), set the relative attribute. +--- +
+

+

+

+

+

+

+
+ + diff --git a/elements/pf-v6-timestamp/demo/timezone.html b/elements/pf-v6-timestamp/demo/timezone.html new file mode 100644 index 0000000000..e3d8ef4bd8 --- /dev/null +++ b/elements/pf-v6-timestamp/demo/timezone.html @@ -0,0 +1,18 @@ +--- +name: Timezone +description: > + Set the time-zone attribute to any IANA timezone identifier to display the + date/time in that timezone. Unlike React's boolean shouldDisplayUTC, this + supports any timezone. Add display-suffix to label the timezone when + time-format does not include timezone information. +--- +
+

+

+

+

+
+ + diff --git a/elements/pf-v6-timestamp/pf-v6-timestamp.css b/elements/pf-v6-timestamp/pf-v6-timestamp.css new file mode 100644 index 0000000000..e5f18d9ef9 --- /dev/null +++ b/elements/pf-v6-timestamp/pf-v6-timestamp.css @@ -0,0 +1,9 @@ +:host { + display: inline-block; + /** Maps to `--pf-t--global--font--size--body--sm` */ + font-size: var(--pf-v6-c-timestamp--FontSize, 0.75rem); + /** Maps to `--pf-t--global--text--color--regular` */ + color: var(--pf-v6-c-timestamp--Color, inherit); + /** Focus ring offset per PatternFly v6 spacing tokens */ + outline-offset: var(--pf-v6-c-timestamp--OutlineOffset, 0.1875rem); +} diff --git a/elements/pf-v6-timestamp/pf-v6-timestamp.ts b/elements/pf-v6-timestamp/pf-v6-timestamp.ts new file mode 100644 index 0000000000..9d5cbe9046 --- /dev/null +++ b/elements/pf-v6-timestamp/pf-v6-timestamp.ts @@ -0,0 +1,117 @@ +import type { PropertyValues, TemplateResult } from 'lit'; + +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import { + TimestampController, + type DateTimeFormat, + type HourCycle, +} from '@patternfly/pfe-core/controllers/timestamp-controller.js'; + +import styles from './pf-v6-timestamp.css'; + +export type { DateTimeFormat, HourCycle }; + +/** + * A timestamp provides consistent formats for displaying date and time values. + * Authors should set `date` to display a specific time. Defaults to now. + * The `