Skip to content

Commit

Permalink
feat(input-time-zone): add offsetStyle prop (#9426)
Browse files Browse the repository at this point in the history
**Related Issue:** #8716

## Summary

Adds `offsetStyle` prop to specify whether offset should always use
`UTC` or `GMT` for the offset label instead of using the user's locale.
Valid options are:

  * `"user"` (default) uses `UTC` or `GMT` depending on the user's locale,
   * `"gmt"` always uses `GMT`, and
   * `"utc"` always uses `UTC`.
  • Loading branch information
jcfranco committed Jun 25, 2024
1 parent fa136a5 commit dbc6c81
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 6 deletions.
112 changes: 110 additions & 2 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,86 +8,184 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { Alignment, Appearance, CollapseDirection, FlipContext, IconType, Kind, Layout, LogicalFlowPosition, Position, Scale, SelectionAppearance as SelectionAppearance1, SelectionMode, Status, Width } from "./components/interfaces";
import { RequestedItem } from "./components/accordion/interfaces";
import { RequestedItem as RequestedItem1 } from "./components/accordion-item/interfaces";
import { ActionMessages } from "./components/action/assets/action/t9n";
import { FlipPlacement, LogicalPlacement, MenuPlacement, OverlayPositioning, ReferenceElement } from "./utils/floating-ui";
import { ActionBarMessages } from "./components/action-bar/assets/action-bar/t9n";
import { Columns } from "./components/action-group/interfaces";
import { ActionGroupMessages } from "./components/action-group/assets/action-group/t9n";
import { ActionPadMessages } from "./components/action-pad/assets/action-pad/t9n";
import { AlertDuration, Sync } from "./components/alert/interfaces";
import { NumberingSystem } from "./utils/locale";
import { AlertMessages } from "./components/alert/assets/alert/t9n";
import { HeadingLevel } from "./components/functional/Heading";
import { BlockMessages } from "./components/block/assets/block/t9n";
import { BlockSectionToggleDisplay } from "./components/block-section/interfaces";
import { BlockSectionMessages } from "./components/block-section/assets/block-section/t9n";
import { ButtonAlignment, DropdownIconType } from "./components/button/interfaces";
import { ButtonMessages } from "./components/button/assets/button/t9n";
import { CardMessages } from "./components/card/assets/card/t9n";
import { ArrowType, AutoplayType } from "./components/carousel/interfaces";
import { CarouselMessages } from "./components/carousel/assets/carousel/t9n";
import { MutableValidityState } from "./utils/form";
import { ChipMessages } from "./components/chip/assets/chip/t9n";
import { ColorValue, InternalColor } from "./components/color-picker/interfaces";
import { Format } from "./components/color-picker/utils";
import { ColorPickerMessages } from "./components/color-picker/assets/color-picker/t9n";
import { ComboboxChildElement, SelectionDisplay } from "./components/combobox/interfaces";
import { ComboboxMessages } from "./components/combobox/assets/combobox/t9n";
import { DatePickerMessages } from "./components/date-picker/assets/date-picker/t9n";
import { DateLocaleData } from "./components/date-picker/utils";
import { HoverRange } from "./utils/date";
import { RequestedItem as RequestedItem2 } from "./components/dropdown-group/interfaces";
import { ItemKeyboardEvent } from "./components/dropdown/interfaces";
import { FilterMessages } from "./components/filter/assets/filter/t9n";
import { FlowItemLikeElement } from "./components/flow/interfaces";
import { FlowItemMessages } from "./components/flow-item/assets/flow-item/t9n";
import { ColorStop, DataSeries } from "./components/graph/interfaces";
import { HandleMessages } from "./components/handle/assets/handle/t9n";
import { HandleChange, HandleNudge } from "./components/handle/interfaces";
import { InlineEditableMessages } from "./components/inline-editable/assets/inline-editable/t9n";
import { InputPlacement } from "./components/input/interfaces";
import { TimeZoneMode } from "./components/input-time-zone/interfaces";
import { InputMessages } from "./components/input/assets/input/t9n";
import { InputDatePickerMessages } from "./components/input-date-picker/assets/input-date-picker/t9n";
import { InputNumberMessages } from "./components/input-number/assets/input-number/t9n";
import { InputTextMessages } from "./components/input-text/assets/input-text/t9n";
import { InputTimePickerMessages } from "./components/input-time-picker/assets/input-time-picker/t9n";
import { TimePickerMessages } from "./components/time-picker/assets/time-picker/t9n";
import { InputTimeZoneMessages } from "./components/input-time-zone/assets/input-time-zone/t9n";
import { OffsetStyle, TimeZoneMode } from "./components/input-time-zone/interfaces";
import { ListDragDetail } from "./components/list/interfaces";
import { ItemData } from "./components/list-item/interfaces";
import { ListMessages } from "./components/list/assets/list/t9n";
import { SelectionAppearance } from "./components/list/resources";
import { ListItemMessages } from "./components/list-item/assets/list-item/t9n";
import { MenuMessages } from "./components/menu/assets/menu/t9n";
import { MenuItemMessages } from "./components/menu-item/assets/menu-item/t9n";
import { MenuItemCustomEvent } from "./components/menu-item/interfaces";
import { MeterFillType, MeterLabelType } from "./components/meter/interfaces";
import { ModalMessages } from "./components/modal/assets/modal/t9n";
import { NoticeMessages } from "./components/notice/assets/notice/t9n";
import { PaginationMessages } from "./components/pagination/assets/pagination/t9n";
import { PanelMessages } from "./components/panel/assets/panel/t9n";
import { ItemData as ItemData1, ListFocusId } from "./components/pick-list/shared-list-logic";
import { ICON_TYPES } from "./components/pick-list/resources";
import { PickListItemMessages } from "./components/pick-list-item/assets/pick-list-item/t9n";
import { PopoverMessages } from "./components/popover/assets/popover/t9n";
import { RatingMessages } from "./components/rating/assets/rating/t9n";
import { ScrimMessages } from "./components/scrim/assets/scrim/t9n";
import { DisplayMode } from "./components/sheet/interfaces";
import { DisplayMode as DisplayMode1 } from "./components/shell-panel/interfaces";
import { ShellPanelMessages } from "./components/shell-panel/assets/shell-panel/t9n";
import { DragDetail } from "./utils/sortableComponent";
import { StepperItemChangeEventDetail, StepperItemEventDetail, StepperItemKeyEventDetail, StepperLayout } from "./components/stepper/interfaces";
import { StepperMessages } from "./components/stepper/assets/stepper/t9n";
import { StepperItemMessages } from "./components/stepper-item/assets/stepper-item/t9n";
import { TabID, TabLayout, TabPosition } from "./components/tabs/interfaces";
import { TabNavMessages } from "./components/tab-nav/assets/tab-nav/t9n";
import { TabChangeEventDetail, TabCloseEventDetail } from "./components/tab/interfaces";
import { TabTitleMessages } from "./components/tab-title/assets/tab-title/t9n";
import { RowType, TableInteractionMode, TableLayout, TableRowFocusEvent, TableSelectionDisplay } from "./components/table/interfaces";
import { TableMessages } from "./components/table/assets/table/t9n";
import { TableCellMessages } from "./components/table-cell/assets/table-cell/t9n";
import { TableHeaderMessages } from "./components/table-header/assets/table-header/t9n";
import { TextAreaMessages } from "./components/text-area/assets/text-area/t9n";
import { TileSelectType } from "./components/tile-select/interfaces";
import { TileSelectGroupLayout } from "./components/tile-select-group/interfaces";
import { TipMessages } from "./components/tip/assets/tip/t9n";
import { TipManagerMessages } from "./components/tip-manager/assets/tip-manager/t9n";
import { TreeItemSelectDetail } from "./components/tree-item/interfaces";
import { ValueListMessages } from "./components/value-list/assets/value-list/t9n";
import { ListItemAndHandle } from "./components/value-list-item/interfaces";
export { Alignment, Appearance, CollapseDirection, FlipContext, IconType, Kind, Layout, LogicalFlowPosition, Position, Scale, SelectionAppearance as SelectionAppearance1, SelectionMode, Status, Width } from "./components/interfaces";
export { RequestedItem } from "./components/accordion/interfaces";
export { RequestedItem as RequestedItem1 } from "./components/accordion-item/interfaces";
export { ActionMessages } from "./components/action/assets/action/t9n";
export { FlipPlacement, LogicalPlacement, MenuPlacement, OverlayPositioning, ReferenceElement } from "./utils/floating-ui";
export { ActionBarMessages } from "./components/action-bar/assets/action-bar/t9n";
export { Columns } from "./components/action-group/interfaces";
export { ActionGroupMessages } from "./components/action-group/assets/action-group/t9n";
export { ActionPadMessages } from "./components/action-pad/assets/action-pad/t9n";
export { AlertDuration, Sync } from "./components/alert/interfaces";
export { NumberingSystem } from "./utils/locale";
export { AlertMessages } from "./components/alert/assets/alert/t9n";
export { HeadingLevel } from "./components/functional/Heading";
export { BlockMessages } from "./components/block/assets/block/t9n";
export { BlockSectionToggleDisplay } from "./components/block-section/interfaces";
export { BlockSectionMessages } from "./components/block-section/assets/block-section/t9n";
export { ButtonAlignment, DropdownIconType } from "./components/button/interfaces";
export { ButtonMessages } from "./components/button/assets/button/t9n";
export { CardMessages } from "./components/card/assets/card/t9n";
export { ArrowType, AutoplayType } from "./components/carousel/interfaces";
export { CarouselMessages } from "./components/carousel/assets/carousel/t9n";
export { MutableValidityState } from "./utils/form";
export { ChipMessages } from "./components/chip/assets/chip/t9n";
export { ColorValue, InternalColor } from "./components/color-picker/interfaces";
export { Format } from "./components/color-picker/utils";
export { ColorPickerMessages } from "./components/color-picker/assets/color-picker/t9n";
export { ComboboxChildElement, SelectionDisplay } from "./components/combobox/interfaces";
export { ComboboxMessages } from "./components/combobox/assets/combobox/t9n";
export { DatePickerMessages } from "./components/date-picker/assets/date-picker/t9n";
export { DateLocaleData } from "./components/date-picker/utils";
export { HoverRange } from "./utils/date";
export { RequestedItem as RequestedItem2 } from "./components/dropdown-group/interfaces";
export { ItemKeyboardEvent } from "./components/dropdown/interfaces";
export { FilterMessages } from "./components/filter/assets/filter/t9n";
export { FlowItemLikeElement } from "./components/flow/interfaces";
export { FlowItemMessages } from "./components/flow-item/assets/flow-item/t9n";
export { ColorStop, DataSeries } from "./components/graph/interfaces";
export { HandleMessages } from "./components/handle/assets/handle/t9n";
export { HandleChange, HandleNudge } from "./components/handle/interfaces";
export { InlineEditableMessages } from "./components/inline-editable/assets/inline-editable/t9n";
export { InputPlacement } from "./components/input/interfaces";
export { TimeZoneMode } from "./components/input-time-zone/interfaces";
export { InputMessages } from "./components/input/assets/input/t9n";
export { InputDatePickerMessages } from "./components/input-date-picker/assets/input-date-picker/t9n";
export { InputNumberMessages } from "./components/input-number/assets/input-number/t9n";
export { InputTextMessages } from "./components/input-text/assets/input-text/t9n";
export { InputTimePickerMessages } from "./components/input-time-picker/assets/input-time-picker/t9n";
export { TimePickerMessages } from "./components/time-picker/assets/time-picker/t9n";
export { InputTimeZoneMessages } from "./components/input-time-zone/assets/input-time-zone/t9n";
export { OffsetStyle, TimeZoneMode } from "./components/input-time-zone/interfaces";
export { ListDragDetail } from "./components/list/interfaces";
export { ItemData } from "./components/list-item/interfaces";
export { ListMessages } from "./components/list/assets/list/t9n";
export { SelectionAppearance } from "./components/list/resources";
export { ListItemMessages } from "./components/list-item/assets/list-item/t9n";
export { MenuMessages } from "./components/menu/assets/menu/t9n";
export { MenuItemMessages } from "./components/menu-item/assets/menu-item/t9n";
export { MenuItemCustomEvent } from "./components/menu-item/interfaces";
export { MeterFillType, MeterLabelType } from "./components/meter/interfaces";
export { ModalMessages } from "./components/modal/assets/modal/t9n";
export { NoticeMessages } from "./components/notice/assets/notice/t9n";
export { PaginationMessages } from "./components/pagination/assets/pagination/t9n";
export { PanelMessages } from "./components/panel/assets/panel/t9n";
export { ItemData as ItemData1, ListFocusId } from "./components/pick-list/shared-list-logic";
export { ICON_TYPES } from "./components/pick-list/resources";
export { PickListItemMessages } from "./components/pick-list-item/assets/pick-list-item/t9n";
export { PopoverMessages } from "./components/popover/assets/popover/t9n";
export { RatingMessages } from "./components/rating/assets/rating/t9n";
export { ScrimMessages } from "./components/scrim/assets/scrim/t9n";
export { DisplayMode } from "./components/sheet/interfaces";
export { DisplayMode as DisplayMode1 } from "./components/shell-panel/interfaces";
export { ShellPanelMessages } from "./components/shell-panel/assets/shell-panel/t9n";
export { DragDetail } from "./utils/sortableComponent";
export { StepperItemChangeEventDetail, StepperItemEventDetail, StepperItemKeyEventDetail, StepperLayout } from "./components/stepper/interfaces";
export { StepperMessages } from "./components/stepper/assets/stepper/t9n";
export { StepperItemMessages } from "./components/stepper-item/assets/stepper-item/t9n";
export { TabID, TabLayout, TabPosition } from "./components/tabs/interfaces";
export { TabNavMessages } from "./components/tab-nav/assets/tab-nav/t9n";
export { TabChangeEventDetail, TabCloseEventDetail } from "./components/tab/interfaces";
export { TabTitleMessages } from "./components/tab-title/assets/tab-title/t9n";
export { RowType, TableInteractionMode, TableLayout, TableRowFocusEvent, TableSelectionDisplay } from "./components/table/interfaces";
export { TableMessages } from "./components/table/assets/table/t9n";
export { TableCellMessages } from "./components/table-cell/assets/table-cell/t9n";
export { TableHeaderMessages } from "./components/table-header/assets/table-header/t9n";
export { TextAreaMessages } from "./components/text-area/assets/text-area/t9n";
export { TileSelectType } from "./components/tile-select/interfaces";
export { TileSelectGroupLayout } from "./components/tile-select-group/interfaces";
export { TipMessages } from "./components/tip/assets/tip/t9n";
export { TipManagerMessages } from "./components/tip-manager/assets/tip-manager/t9n";
export { TreeItemSelectDetail } from "./components/tree-item/interfaces";
export { ValueListMessages } from "./components/value-list/assets/value-list/t9n";
export { ListItemAndHandle } from "./components/value-list-item/interfaces";
export namespace Components {
interface CalciteAccordion {
Expand Down Expand Up @@ -2754,6 +2852,11 @@ export namespace Components {
* Specifies the name of the component. Required to pass the component's `value` on form submission.
*/
"name": string;
/**
* Specifies how the offset will be displayed, where `"user"` uses `UTC` or `GMT` depending on the user's locale, `"gmt"` always uses `GMT`, and `"utc"` always uses `UTC`. This only applies to the `offset` mode.
* @default "user"
*/
"offsetStyle": OffsetStyle;
/**
* When `true`, displays and positions the component.
*/
Expand Down Expand Up @@ -10636,6 +10739,11 @@ declare namespace LocalJSX {
* Specifies the name of the component. Required to pass the component's `value` on form submission.
*/
"name"?: string;
/**
* Specifies how the offset will be displayed, where `"user"` uses `UTC` or `GMT` depending on the user's locale, `"gmt"` always uses `GMT`, and `"utc"` always uses `UTC`. This only applies to the `offset` mode.
* @default "user"
*/
"offsetStyle"?: OffsetStyle;
/**
* Fires when the component is requested to be closed and before the closing transition begins.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import {
import { TagAndPage } from "../../tests/commonTests/interfaces";
import { toUserFriendlyName } from "./utils";

/*
* **Notes**
*
* - tests need to have an emulated time zone
* - test time zones should preferably be unaffected by daylight savings time, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for more info
* - not all time zones are supported in Puppeteer's bundled Chromium, so we patch the Intl API with test-specific values/logic
*/

describe("calcite-input-time-zone", () => {
type TestTimeZoneItem = {
name: string;
Expand Down Expand Up @@ -493,6 +501,67 @@ describe("calcite-input-time-zone", () => {
currComboboxItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item");
expect(currComboboxItem).not.toBe(prevComboboxItem);
});

describe("offsetStyle", () => {
const gmtTimeZoneLocale = "en-GB";
const utcTimeZoneLocale = "fr";

let page: E2EPage;

async function assertItemLabelMatches(page: E2EPage, offsetMarker: "GMT" | "UTC"): Promise<void> {
// all items are formatted equally, so we only need to check the first one
const firstTimeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item");

expect(await firstTimeZoneItem.getProperty("textLabel")).toContain(offsetMarker);
}

beforeEach(async () => {
page = await newE2EPage();
await page.emulateTimezone(testTimeZoneItems[0].name);
});

describe("displays UTC or GMT based on user's locale (default)", () => {
it("displays GMT for GMT-preferred locale", async () => {
await page.setContent(
addTimeZoneNamePolyfill(
html`<calcite-input-time-zone lang="${gmtTimeZoneLocale}"></calcite-input-time-zone>`,
),
);

await assertItemLabelMatches(page, "GMT");
});

it("displays UTC for UTC-preferred locale", async () => {
await page.setContent(
addTimeZoneNamePolyfill(
html`<calcite-input-time-zone lang="${utcTimeZoneLocale}"></calcite-input-time-zone>`,
),
);

await assertItemLabelMatches(page, "UTC");
});
});

it("supports GMT as a style", async () => {
await page.setContent(
addTimeZoneNamePolyfill(
html`<calcite-input-time-zone lang="${utcTimeZoneLocale}" offset-style="gmt"></calcite-input-time-zone>`,
),
);

await assertItemLabelMatches(page, "GMT");
});

it("supports UTC as a style", async () => {
await page.setContent(
addTimeZoneNamePolyfill(
html`<calcite-input-time-zone lang="${gmtTimeZoneLocale}" offset-style="utc"></calcite-input-time-zone>`,
),
);

await assertItemLabelMatches(page, "UTC");
});
});
});

/**
Expand All @@ -511,14 +580,17 @@ function addTimeZoneNamePolyfill(testHtml: string): string {
delete options?.timeZoneName;
super(locales, options);
this.originalOptions = originalOptions;
this.originalLocales = locales;
}
formatToParts(date) {
const originalParts = super.formatToParts(date);
const timeZoneName = this.originalOptions.timeZoneName;
const locale = this.originalLocales;
if (timeZoneName === "shortOffset") {
const { timeZone } = this.originalOptions;
let offsetString;
// hardcoding GMT and time zone names for this particular test suite
Expand All @@ -532,8 +604,10 @@ function addTimeZoneNamePolyfill(testHtml: string): string {
offsetString = offsetString.replace("-", "+");
}
} else {
const offsetMarker = locale === "en-GB" ? "GMT" : locale === "fr" ? "UTC" : "GMT";
offsetString =
"GMT" +
offsetMarker +
(timeZone === "America/Mexico_City" || timeZone === "Pacific/Galapagos"
? "-6"
: timeZone === "America/Phoenix"
Expand Down
Loading

0 comments on commit dbc6c81

Please sign in to comment.