Skip to content

Commit

Permalink
Time - component (HDS-3945) (#2515)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex <alex-ju@users.noreply.github.com>
Co-authored-by: Lee White <leewhite128@gmail.com>
Co-authored-by: Andrew Gendel <124841193+andgen404@users.noreply.github.com>
Co-authored-by: Jory Tindall <jory.tindall@hashicorp.com>
Co-authored-by: Majed <156002572+majedelass@users.noreply.github.com>
  • Loading branch information
6 people authored Dec 9, 2024
1 parent c173c84 commit b25a458
Show file tree
Hide file tree
Showing 38 changed files with 5,536 additions and 5,386 deletions.
7 changes: 7 additions & 0 deletions .changeset/happy-readers-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hashicorp/design-system-components": minor
---

`Time` - Added Time component, Time service, and related libraries including:
- luxon (2.x or 3.x)
- ember-concurrency (4.x)
12 changes: 10 additions & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"ember-a11y-refocus": "^4.1.3",
"ember-cli-sass": "^11.0.1",
"ember-composable-helpers": "^5.0.0",
"ember-concurrency": "^4.0.2",
"ember-element-helper": "^0.8.5",
"ember-focus-trap": "^1.1.0",
"ember-get-config": "^2.1.1",
Expand All @@ -55,6 +56,7 @@
"ember-stargate": "^0.4.3",
"ember-style-modifier": "^4.4.0",
"ember-truth-helpers": "^4.0.3",
"luxon": "^2.3.2 || ^3.4.2",
"prismjs": "^1.29.0",
"sass": "^1.69.5",
"tippy.js": "^6.3.7"
Expand All @@ -78,6 +80,7 @@
"@types/ember-qunit": "^6.1.1",
"@types/ember-resolver": "^9.0.0",
"@types/ember__destroyable": "^4.0.5",
"@types/luxon": "^3.2.0",
"@types/prismjs": "^1.26.4",
"@types/qunit": "^2.19.10",
"@types/rsvp": "^4.0.9",
Expand All @@ -86,7 +89,6 @@
"babel-plugin-ember-template-compilation": "^2.2.4",
"concurrently": "^8.2.2",
"ember-basic-dropdown": "^8.1.0",
"ember-concurrency": "^4.0.2",
"ember-source": "~5.9.0",
"ember-template-lint": "^6.0.0",
"ember-template-lint-plugin-prettier": "^5.0.0",
Expand Down Expand Up @@ -286,16 +288,22 @@
"./components/hds/text/code.js": "./dist/_app_/components/hds/text/code.js",
"./components/hds/text/display.js": "./dist/_app_/components/hds/text/display.js",
"./components/hds/text/index.js": "./dist/_app_/components/hds/text/index.js",
"./components/hds/time/index.js": "./dist/_app_/components/hds/time/index.js",
"./components/hds/time/range.js": "./dist/_app_/components/hds/time/range.js",
"./components/hds/time/single.js": "./dist/_app_/components/hds/time/single.js",
"./components/hds/toast/index.js": "./dist/_app_/components/hds/toast/index.js",
"./components/hds/tooltip-button/index.js": "./dist/_app_/components/hds/tooltip-button/index.js",
"./components/hds/yield/index.js": "./dist/_app_/components/hds/yield/index.js",
"./helpers/hds-format-date.js": "./dist/_app_/helpers/hds-format-date.js",
"./helpers/hds-format-relative.js": "./dist/_app_/helpers/hds-format-relative.js",
"./helpers/hds-link-to-models.js": "./dist/_app_/helpers/hds-link-to-models.js",
"./helpers/hds-link-to-query.js": "./dist/_app_/helpers/hds-link-to-query.js",
"./instance-initializers/load-sprite.js": "./dist/_app_/instance-initializers/load-sprite.js",
"./modifiers/hds-anchored-position.js": "./dist/_app_/modifiers/hds-anchored-position.js",
"./modifiers/hds-clipboard.js": "./dist/_app_/modifiers/hds-clipboard.js",
"./modifiers/hds-register-event.js": "./dist/_app_/modifiers/hds-register-event.js",
"./modifiers/hds-tooltip.js": "./dist/_app_/modifiers/hds-tooltip.js"
"./modifiers/hds-tooltip.js": "./dist/_app_/modifiers/hds-tooltip.js",
"./services/hds-time.js": "./dist/_app_/services/hds-time.js"
}
},
"exports": {
Expand Down
13 changes: 8 additions & 5 deletions packages/components/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ const plugins = [
// These are the modules that should get reexported into the traditional
// "app" tree. Things in here should also be in publicEntrypoints above, but
// not everything in publicEntrypoints necessarily needs to go here.
addon.appReexports([
'components/**/!(*types).js',
'helpers/**/*.js',
'modifiers/**/*.js',
'instance-initializers/**/*.js'],
addon.appReexports(
[
'components/**/!(*types).js',
'helpers/**/*.js',
'modifiers/**/*.js',
'services/**/!(*types).js',
'instance-initializers/**/*.js',
],
{
exclude: [
'components/**/app-header/**/*.js',
Expand Down
6 changes: 6 additions & 0 deletions packages/components/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ export { default as HdsTextCode } from './components/hds/text/code.ts';
export { default as HdsTextDisplay } from './components/hds/text/display.ts';
export * from './components/hds/text/types.ts';

// Time
export { default as HdsTime } from './components/hds/time/index.ts';
export { default as HdsTimeSingle } from './components/hds/time/single.ts';
export { default as HdsTimeRange } from './components/hds/time/range.ts';
export * from './services/hds-time-types.ts';

// Toast
export { default as HdsToast } from './components/hds/toast/index.ts';

Expand Down
59 changes: 59 additions & 0 deletions packages/components/src/components/hds/time/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
{{! IMPORTANT: we need to add "squishies" here (~) because otherwise the whitespace added by Ember causes extra space around the time element - See https://handlebarsjs.com/guide/expressions.html#whitespace-control }}
{{~#let this.display as |display|~}}
{{~#if this.isValidDate~}}
{{~#if this.hasTooltip~}}
<Hds::TooltipButton
class="hds-time-wrapper"
@text={{if
display.options.tooltipFormat
(hds-format-date this.date display.options.tooltipFormat)
this.isoUtcString
}}
@placement="bottom"
@extraTippyOptions={{hash showOnCreate=this.isOpen}}
>
<Hds::Time::Single
@date={{this.date}}
@isoUtcString={{this.isoUtcString}}
@display={{this.display}}
@register={{this.didInsertNode}}
@unregister={{this.willDestroyNode}}
...attributes
/>
</Hds::TooltipButton>
{{~else~}}
<Hds::Time::Single
@date={{this.date}}
@isoUtcString={{this.isoUtcString}}
@display={{this.display}}
@register={{this.didInsertNode}}
@unregister={{this.willDestroyNode}}
...attributes
/>
{{~/if~}}
{{~else if this.isValidDateRange~}}
{{~#if this.hasTooltip~}}
<Hds::TooltipButton
class="hds-time-wrapper"
@text={{if
display.options.tooltipFormat
(concat
(hds-format-date this.startDate display.options.tooltipFormat)
(hds-format-date this.endDate display.options.tooltipFormat)
)
this.rangeIsoUtcString
}}
@placement="bottom"
@extraTippyOptions={{hash showOnCreate=this.isOpen}}
>
<Hds::Time::Range @startDate={{this.startDate}} @endDate={{this.endDate}} ...attributes />
</Hds::TooltipButton>
{{~else~}}
<Hds::Time::Range @startDate={{this.startDate}} @endDate={{this.endDate}} ...attributes />
{{~/if~}}
{{~/if~}}
{{~/let~}}
146 changes: 146 additions & 0 deletions packages/components/src/components/hds/time/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import { typeOf } from '@ember/utils';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import type { DisplayType } from '../../../services/hds-time-types.ts';

import type TimeService from '../../../services/hds-time';

export interface HdsTimeSignature {
Args: {
date?: Date | string;
startDate?: Date | string;
endDate?: Date | string;
display?:
| 'utc'
| 'relative'
| 'friendly-only'
| 'friendly-local'
| 'friendly-relative';
isOpen?: boolean;
hasTooltip?: boolean;
isoUtcString?: string;
};
Element: HTMLElement;
}

const dateIsValid = (date?: Date | string): date is Date =>
date instanceof Date && !isNaN(+date);

export default class HdsTime extends Component<HdsTimeSignature> {
@service declare readonly hdsTime: TimeService;

get date(): Date | undefined {
const { date } = this.args;

// Sometimes an ISO date string might be passed in instead of a JS Date.
if (date) {
if (typeOf(date) === 'string') {
return new Date(date);
} else if (date instanceof Date) {
return date;
}
}
}

get startDate(): Date | undefined {
const { startDate } = this.args;

if (startDate) {
if (typeOf(startDate) === 'string') {
return new Date(startDate);
} else if (startDate instanceof Date) {
return startDate;
}
}
}

get endDate(): Date | undefined {
const { endDate } = this.args;

if (endDate) {
if (typeOf(endDate) === 'string') {
return new Date(endDate);
} else if (endDate instanceof Date) {
return endDate;
}
}
}

get isValidDate(): boolean {
return dateIsValid(this.date);
}

get isValidDateRange(): boolean {
if (dateIsValid(this.startDate) && dateIsValid(this.endDate)) {
return this.startDate <= this.endDate;
}
return false;
}

get hasTooltip(): boolean {
return this.args.hasTooltip ?? true;
}

get isoUtcString(): string | undefined {
const date = this.date;

if (dateIsValid(date)) {
return this.hdsTime.toIsoUtcString(date);
}

return undefined;
}

get rangeIsoUtcString(): string {
const startDate = this.startDate;
const endDate = this.endDate;

if (dateIsValid(startDate) && dateIsValid(endDate)) {
return `${this.hdsTime.toIsoUtcString(startDate)}${this.hdsTime.toIsoUtcString(endDate)}`;
}
return '';
}

get display(): DisplayType {
const date = this.date;
const { display } = this.args;

if (dateIsValid(date)) {
const nextDiff = this.hdsTime.timeDifference(this.hdsTime.now, date);
return this.hdsTime.format(nextDiff, display);
}
return {
options: undefined,
difference: { absValueInMs: 0, valueInMs: 0 },
relative: { value: 0, unit: '' },
};
}

get isOpen(): boolean {
return this.args.isOpen ?? false;
}

@action
didInsertNode(): void {
const date = this.date;

if (dateIsValid(date)) {
this.hdsTime.register(date);
}
}

@action
willDestroyNode(): void {
const date = this.date;

if (dateIsValid(date)) {
this.hdsTime.unregister(date);
}
}
}
14 changes: 14 additions & 0 deletions packages/components/src/components/hds/time/range.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
{{! IMPORTANT: we need to add "squishies" here (~) because otherwise the whitespace added by Ember causes extra space around the time element - See https://handlebarsjs.com/guide/expressions.html#whitespace-control }}
<span class="hds-time hds-time--range" ...attributes>
<time datetime={{this.startDateIsoUtcString}}>
{{~hds-format-date @startDate this.startDateDisplayFormat~}}
</time>
<time datetime={{this.endDateIsoUtcString}}>
{{~hds-format-date @endDate this.endDateDisplayFormat~}}
</time>
</span>
74 changes: 74 additions & 0 deletions packages/components/src/components/hds/time/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import type TimeService from '../../../services/hds-time';

export interface HdsTimeRangeSignature {
Args: {
startDate?: Date;
endDate?: Date;
};
Element: HTMLElement;
}

export default class HdsTimeRange extends Component<HdsTimeRangeSignature> {
@service declare readonly hdsTime: TimeService;

get startDateIsoUtcString(): string | undefined {
const { startDate } = this.args;
if (startDate) {
return this.hdsTime.toIsoUtcString(startDate);
}
}

get endDateIsoUtcString(): string | undefined {
const { endDate } = this.args;
if (endDate) {
return this.hdsTime.toIsoUtcString(endDate);
}
}

get startDateDisplayFormat(): {
month: Intl.DateTimeFormatOptions['month'];
day: Intl.DateTimeFormatOptions['day'];
year?: Intl.DateTimeFormatOptions['year'];
hour?: Intl.DateTimeFormatOptions['hour'];
minute?: Intl.DateTimeFormatOptions['minute'];
second?: Intl.DateTimeFormatOptions['second'];
} {
const { startDate, endDate } = this.args;

if (startDate?.getFullYear() !== endDate?.getFullYear()) {
return {
month: 'short',
day: 'numeric',
year: 'numeric',
};
} else {
return {
month: 'short',
day: 'numeric',
year: undefined,
};
}
}

get endDateDisplayFormat(): {
month: Intl.DateTimeFormatOptions['month'];
day: Intl.DateTimeFormatOptions['day'];
year?: Intl.DateTimeFormatOptions['year'];
hour?: Intl.DateTimeFormatOptions['hour'];
minute?: Intl.DateTimeFormatOptions['minute'];
second?: Intl.DateTimeFormatOptions['second'];
} {
return {
month: 'short',
day: 'numeric',
year: 'numeric',
};
}
}
Loading

0 comments on commit b25a458

Please sign in to comment.