-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HDS-3945 Implement initial Time component based on Cloud-ui and add S…
…howcase
- Loading branch information
1 parent
6fe63c5
commit 880c9b4
Showing
13 changed files
with
979 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{{! | ||
Copyright (c) HashiCorp, Inc. | ||
SPDX-License-Identifier: MPL-2.0 | ||
}} | ||
|
||
{{! @glint-nocheck }} | ||
{{! TODO: format-date & display have type errors, fix }} | ||
{{#let this.display as |display|}} | ||
{{#if (and this.hasTooltip this.isValid)}} | ||
<Hds::TooltipButton | ||
@text={{if | ||
display.options.tooltipFormat | ||
(format-date | ||
this.date | ||
month=display.options.tooltipFormat.month | ||
day=display.options.tooltipFormat.day | ||
year=display.options.tooltipFormat.year | ||
hour=display.options.tooltipFormat.hour | ||
minute=display.options.tooltipFormat.minute | ||
second=display.options.tooltipFormat.second | ||
) | ||
this.isoUtcString | ||
}} | ||
@placement="bottom" | ||
@extraTippyOptions={{hash showOnCreate=this.isOpen}} | ||
> | ||
<Hds::Time::Inner | ||
@date={{this.date}} | ||
@isoUtcString={{this.isoUtcString}} | ||
@display={{this.display}} | ||
@isValid={{this.isValid}} | ||
...attributes | ||
/> | ||
</Hds::TooltipButton> | ||
{{else}} | ||
<Hds::Time::Inner | ||
@date={{this.date}} | ||
@isoUtcString={{this.isoUtcString}} | ||
@display={{this.display}} | ||
@isValid={{this.isValid}} | ||
...attributes | ||
/> | ||
{{/if}} | ||
{{/let}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
/** | ||
* Copyright (c) HashiCorp, Inc. | ||
* SPDX-License-Identifier: MPL-2.0 | ||
*/ | ||
|
||
import Component from '@glimmer/component'; | ||
import { typeOf } from '@ember/utils'; | ||
import { DateTime } from 'luxon'; | ||
|
||
const MILLISECOND_IN_MS = 1; | ||
const SECOND_IN_MS = 1000 * MILLISECOND_IN_MS; | ||
const MINUTE_IN_MS = 60 * SECOND_IN_MS; | ||
const HOUR_IN_MS = 60 * MINUTE_IN_MS; | ||
const DAY_IN_MS = 24 * HOUR_IN_MS; | ||
const WEEK_IN_MS = 7 * DAY_IN_MS; | ||
|
||
const THRESHOLD_RELATIVE_TIME_IN_MS = WEEK_IN_MS; | ||
|
||
const RELATIVE_UNIT_SECOND = 'second'; | ||
const RELATIVE_UNIT_HOUR = 'hour'; | ||
const RELATIVE_UNIT_MINUTE = 'minute'; | ||
const RELATIVE_UNIT_DAY = 'day'; | ||
const RELATIVE_UNIT_WEEK = 'week'; | ||
|
||
const DEFAULT_RELATIVE_THRESHOLDS = { | ||
[RELATIVE_UNIT_SECOND]: 1 * MINUTE_IN_MS, | ||
[RELATIVE_UNIT_MINUTE]: 1 * HOUR_IN_MS, | ||
[RELATIVE_UNIT_HOUR]: 1 * DAY_IN_MS, | ||
[RELATIVE_UNIT_DAY]: 100 * WEEK_IN_MS, | ||
}; | ||
|
||
// returns 'Sep 5, 2018 (30 minutes ago)' | ||
const DISPLAY_KEY_FRIENDLY_RELATIVE = 'friendly-relative'; | ||
|
||
// returns 'Sep 5, 2018, 4:07:32 pm' | ||
const DISPLAY_KEY_FRIENDLY_LOCAL = 'friendly-local'; | ||
|
||
// returns 'Sep 5, 2018' | ||
const DISPLAY_KEY_FRIENDLY_ONLY = 'friendly-only'; | ||
|
||
// returns 'about 2 hours ago' | ||
const DISPLAY_KEY_RELATIVE = 'relative'; | ||
|
||
// returns '2018-09-05T23:15:17345Z' | ||
const DISPLAY_KEY_UTC = 'utc'; | ||
|
||
const FORMAT_PRECISION_SHORT_DATE = { | ||
month: 'short', | ||
day: 'numeric', | ||
year: 'numeric', | ||
}; | ||
const FORMAT_PRECISION_MINUTE = { | ||
...FORMAT_PRECISION_SHORT_DATE, | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
}; | ||
const FORMAT_PRECISION_SECOND = { | ||
...FORMAT_PRECISION_SHORT_DATE, | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
second: 'numeric', | ||
}; | ||
const DATE_DISPLAY_FORMATS = { | ||
[DISPLAY_KEY_FRIENDLY_LOCAL]: FORMAT_PRECISION_SECOND, | ||
[DISPLAY_KEY_FRIENDLY_ONLY]: FORMAT_PRECISION_SHORT_DATE, | ||
}; | ||
|
||
const DEFAULT_DISPLAY = ''; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const DEFAULT_DISPLAY_MAPPING: any = { | ||
[DISPLAY_KEY_FRIENDLY_RELATIVE]: { | ||
displayFormat: FORMAT_PRECISION_SHORT_DATE, | ||
showFriendly: true, | ||
showRelative: true, | ||
tooltipFormat: FORMAT_PRECISION_SECOND, | ||
}, | ||
[DISPLAY_KEY_FRIENDLY_LOCAL]: { | ||
displayFormat: DATE_DISPLAY_FORMATS[DISPLAY_KEY_FRIENDLY_LOCAL], | ||
showFriendly: true, | ||
showRelative: false, | ||
tooltipFormat: null, | ||
}, | ||
[DISPLAY_KEY_FRIENDLY_ONLY]: { | ||
displayFormat: DATE_DISPLAY_FORMATS[DISPLAY_KEY_FRIENDLY_ONLY], | ||
showFriendly: true, | ||
showRelative: false, | ||
tooltipFormat: null, | ||
}, | ||
[DISPLAY_KEY_RELATIVE]: { | ||
displayFormat: null, | ||
showFriendly: false, | ||
showRelative: true, | ||
tooltipFormat: FORMAT_PRECISION_MINUTE, | ||
}, | ||
[DISPLAY_KEY_UTC]: { | ||
displayFormat: null, | ||
showFriendly: true, | ||
showRelative: false, | ||
tooltipFormat: null, | ||
}, | ||
}; | ||
const DISPLAY_SCALE = Object.keys(DEFAULT_DISPLAY_MAPPING); | ||
|
||
export const DISPLAYS: string[] = [ | ||
DISPLAY_KEY_FRIENDLY_RELATIVE, | ||
DISPLAY_KEY_FRIENDLY_LOCAL, | ||
DISPLAY_KEY_FRIENDLY_ONLY, | ||
DISPLAY_KEY_RELATIVE, | ||
DISPLAY_KEY_UTC, | ||
]; | ||
|
||
export interface HdsTimeSignature { | ||
Args: { | ||
date: Date | string | undefined; | ||
display?: string; | ||
isOpen?: boolean; | ||
hasTooltip?: boolean; | ||
}; | ||
Element: HTMLElement; | ||
} | ||
|
||
const dateIsValid = (date?: Date | string): date is Date => | ||
date instanceof Date && !isNaN(+date); | ||
|
||
export default class HdsTime extends Component<HdsTimeSignature> { | ||
now = Date.now(); | ||
|
||
format( | ||
difference: { absValueInMs: number; valueInMs: number }, | ||
display: string = DEFAULT_DISPLAY | ||
): { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
options: any; | ||
difference: { absValueInMs: number; valueInMs: number }; | ||
relative: { value: number; unit: string }; | ||
} { | ||
let displayKey: string; | ||
|
||
// If the scale display is defined and valid then set that display. | ||
if (display && DISPLAY_SCALE.includes(display)) { | ||
displayKey = display; | ||
} else { | ||
// If there's no defined display then we will execute the design system's | ||
// prefered algorithm. | ||
|
||
// By default, we assume we will display just a relative display only. | ||
displayKey = DISPLAY_KEY_RELATIVE; | ||
|
||
// If the difference in date is greater than the threshold for showing the | ||
// relative time then switch the display key. | ||
if (difference.absValueInMs > THRESHOLD_RELATIVE_TIME_IN_MS) { | ||
displayKey = DISPLAY_KEY_FRIENDLY_LOCAL; | ||
} | ||
} | ||
|
||
// TODO: Not sure how to determine type (defined as "any" for now) | ||
const options = DEFAULT_DISPLAY_MAPPING[displayKey]; | ||
|
||
return { | ||
options, | ||
difference, | ||
relative: this.selectTimeRelativeUnit(difference), | ||
}; | ||
} | ||
|
||
// Formats the value of a relative unit. | ||
formatTimeRelativeUnit( | ||
value: number, | ||
unit: string | ||
): { value: number; unit: string } { | ||
return { | ||
value: Math.trunc(value), | ||
unit, | ||
}; | ||
} | ||
|
||
// Selects an appropriate display format for the difference. | ||
selectTimeRelativeUnit( | ||
{ absValueInMs, valueInMs }: { absValueInMs: number; valueInMs: number }, | ||
thresholds = DEFAULT_RELATIVE_THRESHOLDS | ||
): { value: number; unit: string } { | ||
if (absValueInMs < thresholds[RELATIVE_UNIT_SECOND]) { | ||
return this.formatTimeRelativeUnit( | ||
valueInMs / SECOND_IN_MS, | ||
RELATIVE_UNIT_SECOND | ||
); | ||
} | ||
|
||
if (absValueInMs < thresholds[RELATIVE_UNIT_MINUTE]) { | ||
return this.formatTimeRelativeUnit( | ||
valueInMs / MINUTE_IN_MS, | ||
RELATIVE_UNIT_MINUTE | ||
); | ||
} | ||
|
||
if (absValueInMs < thresholds[RELATIVE_UNIT_HOUR]) { | ||
return this.formatTimeRelativeUnit( | ||
valueInMs / HOUR_IN_MS, | ||
RELATIVE_UNIT_HOUR | ||
); | ||
} | ||
|
||
if (absValueInMs < thresholds[RELATIVE_UNIT_DAY]) { | ||
return this.formatTimeRelativeUnit( | ||
valueInMs / DAY_IN_MS, | ||
RELATIVE_UNIT_DAY | ||
); | ||
} | ||
|
||
return this.formatTimeRelativeUnit( | ||
valueInMs / WEEK_IN_MS, | ||
RELATIVE_UNIT_WEEK | ||
); | ||
} | ||
|
||
// Gets the currently subscribed listeners. | ||
timeDifference( | ||
startDate: Date | number, | ||
endDate: Date | number | ||
): { absValueInMs: number; valueInMs: number } { | ||
const valueInMs = Number(endDate) - Number(startDate); | ||
return { | ||
absValueInMs: Math.abs(valueInMs), | ||
valueInMs, | ||
}; | ||
} | ||
|
||
get date(): string | Date | undefined { | ||
const { date } = this.args; | ||
|
||
// Sometimes an ISO date string might be passed in instead of a JS Date. | ||
if (typeOf(date) === 'string') { | ||
return new Date(date as string); | ||
} | ||
return date; | ||
} | ||
|
||
get isValid(): boolean { | ||
return dateIsValid(this.date); | ||
} | ||
|
||
get hasTooltip(): boolean { | ||
return this.args.hasTooltip ?? true; | ||
} | ||
|
||
get isoUtcString(): string { | ||
const date = this.date; | ||
|
||
// if (dateIsValid(date)) return this.time.toIsoUtcString(date); | ||
if (dateIsValid(date)) { | ||
return DateTime.fromJSDate(date).toUTC().toJSDate().toISOString(); | ||
} | ||
return ''; | ||
} | ||
|
||
get display(): { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
options: any; | ||
difference: { absValueInMs: number; valueInMs: number }; | ||
relative: { value: number; unit: string }; | ||
} { | ||
const date = this.date; | ||
const { display } = this.args; | ||
if (dateIsValid(date)) { | ||
const nextDiff = this.timeDifference(this.now, date); | ||
return this.format(nextDiff, display); | ||
} | ||
return { | ||
options: {}, | ||
difference: { absValueInMs: 0, valueInMs: 0 }, | ||
relative: { value: 0, unit: '' }, | ||
}; | ||
} | ||
|
||
get isOpen(): boolean { | ||
return this.args.isOpen ?? false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{{! | ||
Copyright (c) HashiCorp, Inc. | ||
SPDX-License-Identifier: MPL-2.0 | ||
}} | ||
|
||
{{! @glint-nocheck: not typesafe yet }} | ||
{{! TODO: format-date & format-relative have type errors, fix }} | ||
{{#if @isValid}} | ||
<time class="hds-time" datetime={{@isoUtcString}} ...attributes> | ||
{{#if @display.options.showFriendly}} | ||
<span data-test-time-friendly> | ||
{{#if @display.options.displayFormat}} | ||
{{format-date | ||
@date | ||
month=@display.options.displayFormat.month | ||
day=@display.options.displayFormat.day | ||
year=@display.options.displayFormat.year | ||
hour=@display.options.displayFormat.hour | ||
minute=@display.options.displayFormat.minute | ||
second=@display.options.displayFormat.second | ||
}} | ||
{{else}} | ||
{{@isoUtcString}} | ||
{{/if}} | ||
</span> | ||
{{#if @display.options.showRelative}} | ||
<span data-test-time-relative> | ||
({{format-relative @display.relative.value unit=@display.relative.unit}}) | ||
</span> | ||
{{/if}} | ||
{{else}} | ||
{{#if @display.options.showRelative}} | ||
<span data-test-time-relative> | ||
{{format-relative @display.relative.value unit=@display.relative.unit}} | ||
</span> | ||
{{/if}} | ||
{{/if}} | ||
</time> | ||
{{else}} | ||
<span data-test-time-invalid>--</span> | ||
{{/if}} |
Oops, something went wrong.