From c7c32f8f80cd1659b3fb0a5d92353e19aa9e8a4a Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 26 Jul 2024 15:38:26 -0400 Subject: [PATCH 01/15] add basic datetime guy --- package.json | 1 + src/components/DateTime/DateTime.stories.tsx | 15 +++ src/components/DateTime/DateTime.tsx | 97 ++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/components/DateTime/DateTime.stories.tsx create mode 100644 src/components/DateTime/DateTime.tsx diff --git a/package.json b/package.json index cf7dae0a..cbcd504c 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", + "dayjs": "^1.11.12", "lodash": "^4.17.21", "react-sortablejs": "^6.1.4", "react-syntax-highlighter": "^15.5.0", diff --git a/src/components/DateTime/DateTime.stories.tsx b/src/components/DateTime/DateTime.stories.tsx new file mode 100644 index 00000000..9999d075 --- /dev/null +++ b/src/components/DateTime/DateTime.stories.tsx @@ -0,0 +1,15 @@ +import { DateTime } from "./DateTime"; + +export default { + component: DateTime, + title: "Display/DateTime", + tags: ["autodocs"], +}; + +export const Playground = { + args: { + date: new Date(), + systemTimeZone: "America/Los_Angeles", + title: "DateTime", + }, +}; diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx new file mode 100644 index 00000000..7e476b15 --- /dev/null +++ b/src/components/DateTime/DateTime.tsx @@ -0,0 +1,97 @@ +import dayjs from 'dayjs'; +import duration, { DurationUnitType } from 'dayjs/plugin/duration'; +import localizedFormat from 'dayjs/plugin/localizedFormat'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import timezone from 'dayjs/plugin/timezone'; +import updateLocale from 'dayjs/plugin/updateLocale'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(duration); +dayjs.extend(localizedFormat); +dayjs.extend(updateLocale); +dayjs.extend(utc); + +const thresholds = [ + { l: 's', r: 1, d: 'second' }, + { l: 'ss', r: 56, d: 'second' }, + { l: 'm', r: 90, d: 'second' }, + { l: 'mm', r: 55, d: 'minute' }, + { l: 'h', r: 90, d: 'minute' }, + { l: 'hh', r: 22, d: 'hour' }, + { l: 'd', r: 40, d: 'hour' }, + { l: 'dd', r: 31, d: 'day' }, + { l: 'M', r: 45, d: 'day' }, + { l: 'MM', r: 11, d: 'month' }, + { l: 'y', r: 17, d: 'month' }, + { l: 'yy', r: 2, d: 'year' } +]; + +dayjs.extend(relativeTime, { thresholds }); + +dayjs.updateLocale('en', { + relativeTime: { + future: 'In %s', + past: '%s ago', + s: 'a few seconds', + ss: '%d seconds', + m: '1 minute', + mm: '%d minutes', + h: '1 hour', + hh: '%d hours', + d: '1 day', + dd: '%d days', + w: '1 week', + ww: '%d weeks', + M: '1 month', + MM: '%d months', + y: '1 year', + yy: '%d years' + } +}); + +import { Container } from "../Container/Container"; +import { Panel } from "../Panel/Panel"; +import { Popover } from "../Popover/Popover"; + +export interface DateTimeProps { + date: Date; + systemTimeZone?: string; +} + +export const DateTime = ({ date, systemTimeZone }: DateTimeProps) => { + console.log(systemTimeZone); + + const FORMAT = 'YYYY-MM-DD hh:mm:ss'; + + const dayjsDate = dayjs(date); + + let systemTime; + if (systemTimeZone) { + dayjs.extend(timezone); + systemTime = dayjsDate.tz(systemTimeZone) + } + + return ( + + {dayjs.utc(date).fromNow()} + + + + {date.getTime()} + + + UTC: <>{dayjsDate.utc().format(FORMAT)} + + + Local: {dayjsDate.format(FORMAT)} + + {systemTime && ( + + System: <>{systemTime.format(FORMAT)} + + )} + + + + ) +} From d5a828979e1757326bdfad431296f158086da6fd Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Mon, 29 Jul 2024 14:57:42 -0400 Subject: [PATCH 02/15] clean up typography --- src/components/DateTime/DateTime.tsx | 141 +++++++++++++++------------ src/components/Popover/Popover.tsx | 7 +- 2 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index 7e476b15..79f16355 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -1,10 +1,10 @@ -import dayjs from 'dayjs'; -import duration, { DurationUnitType } from 'dayjs/plugin/duration'; -import localizedFormat from 'dayjs/plugin/localizedFormat'; -import relativeTime from 'dayjs/plugin/relativeTime'; -import timezone from 'dayjs/plugin/timezone'; -import updateLocale from 'dayjs/plugin/updateLocale'; -import utc from 'dayjs/plugin/utc'; +import dayjs from "dayjs"; +import duration, { DurationUnitType } from "dayjs/plugin/duration"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import relativeTime from "dayjs/plugin/relativeTime"; +import timezone from "dayjs/plugin/timezone"; +import updateLocale from "dayjs/plugin/updateLocale"; +import utc from "dayjs/plugin/utc"; dayjs.extend(duration); dayjs.extend(localizedFormat); @@ -12,46 +12,47 @@ dayjs.extend(updateLocale); dayjs.extend(utc); const thresholds = [ - { l: 's', r: 1, d: 'second' }, - { l: 'ss', r: 56, d: 'second' }, - { l: 'm', r: 90, d: 'second' }, - { l: 'mm', r: 55, d: 'minute' }, - { l: 'h', r: 90, d: 'minute' }, - { l: 'hh', r: 22, d: 'hour' }, - { l: 'd', r: 40, d: 'hour' }, - { l: 'dd', r: 31, d: 'day' }, - { l: 'M', r: 45, d: 'day' }, - { l: 'MM', r: 11, d: 'month' }, - { l: 'y', r: 17, d: 'month' }, - { l: 'yy', r: 2, d: 'year' } + { l: "s", r: 1, d: "second" }, + { l: "ss", r: 56, d: "second" }, + { l: "m", r: 90, d: "second" }, + { l: "mm", r: 55, d: "minute" }, + { l: "h", r: 90, d: "minute" }, + { l: "hh", r: 22, d: "hour" }, + { l: "d", r: 40, d: "hour" }, + { l: "dd", r: 31, d: "day" }, + { l: "M", r: 45, d: "day" }, + { l: "MM", r: 11, d: "month" }, + { l: "y", r: 17, d: "month" }, + { l: "yy", r: 2, d: "year" }, ]; dayjs.extend(relativeTime, { thresholds }); -dayjs.updateLocale('en', { +dayjs.updateLocale("en", { relativeTime: { - future: 'In %s', - past: '%s ago', - s: 'a few seconds', - ss: '%d seconds', - m: '1 minute', - mm: '%d minutes', - h: '1 hour', - hh: '%d hours', - d: '1 day', - dd: '%d days', - w: '1 week', - ww: '%d weeks', - M: '1 month', - MM: '%d months', - y: '1 year', - yy: '%d years' - } + future: "In %s", + past: "%s ago", + s: "a few seconds", + ss: "%d seconds", + m: "1 minute", + mm: "%d minutes", + h: "1 hour", + hh: "%d hours", + d: "1 day", + dd: "%d days", + w: "1 week", + ww: "%d weeks", + M: "1 month", + MM: "%d months", + y: "1 year", + yy: "%d years", + }, }); import { Container } from "../Container/Container"; import { Panel } from "../Panel/Panel"; import { Popover } from "../Popover/Popover"; +import { Text } from "../Typography/Text/Text"; export interface DateTimeProps { date: Date; @@ -61,37 +62,57 @@ export interface DateTimeProps { export const DateTime = ({ date, systemTimeZone }: DateTimeProps) => { console.log(systemTimeZone); - const FORMAT = 'YYYY-MM-DD hh:mm:ss'; + const FORMAT = "YYYY-MM-DD hh:mm:ss"; const dayjsDate = dayjs(date); let systemTime; if (systemTimeZone) { dayjs.extend(timezone); - systemTime = dayjsDate.tz(systemTimeZone) + systemTime = dayjsDate.tz(systemTimeZone); } - return ( - - {dayjs.utc(date).fromNow()} - - - - {date.getTime()} - - - UTC: <>{dayjsDate.utc().format(FORMAT)} - + return ( + + + {dayjs.utc(date).fromNow()} + + + - Local: {dayjsDate.format(FORMAT)} + {date.getTime()} - {systemTime && ( - - System: <>{systemTime.format(FORMAT)} + + + UTC: + {dayjsDate.utc().format(FORMAT)} - )} - - - - ) -} + + Local: + {dayjsDate.format(FORMAT)} + + {systemTime && ( + + System: + {systemTime.format(FORMAT)} + + )} + + + + + ); +}; diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx index 71d2a0c1..68887a7f 100644 --- a/src/components/Popover/Popover.tsx +++ b/src/components/Popover/Popover.tsx @@ -11,11 +11,12 @@ export const Popover = ({ children, ...props }: RadixPopover.PopoverProps) => { }; const Trigger = styled(RadixPopover.Trigger)` - width: fit-content; - font: inherit; - color: inherit; background: inherit; border: none; + color: inherit; + cursor: pointer; + font: inherit; + width: fit-content; `; interface TriggerProps extends RadixPopover.PopoverTriggerProps { anchor?: ReactNode; From c2b1f9d8ca413705e757861400f9278cef757f42 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Mon, 29 Jul 2024 15:50:57 -0400 Subject: [PATCH 03/15] use intl dateformatter --- src/components/DateTime/DateTime.stories.tsx | 1 + src/components/DateTime/DateTime.tsx | 74 +++++++++++++++++--- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/components/DateTime/DateTime.stories.tsx b/src/components/DateTime/DateTime.stories.tsx index 9999d075..5becc492 100644 --- a/src/components/DateTime/DateTime.stories.tsx +++ b/src/components/DateTime/DateTime.stories.tsx @@ -9,6 +9,7 @@ export default { export const Playground = { args: { date: new Date(), + locale: 'en-US', systemTimeZone: "America/Los_Angeles", title: "DateTime", }, diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index 79f16355..9341180c 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import duration, { DurationUnitType } from "dayjs/plugin/duration"; +import duration from "dayjs/plugin/duration"; import localizedFormat from "dayjs/plugin/localizedFormat"; import relativeTime from "dayjs/plugin/relativeTime"; import timezone from "dayjs/plugin/timezone"; @@ -54,13 +54,60 @@ import { Panel } from "../Panel/Panel"; import { Popover } from "../Popover/Popover"; import { Text } from "../Typography/Text/Text"; +const dateStyle = "medium"; +const timeStyle = "medium"; + +const createBasicDateTimeFormatter = () => { + return new Intl.DateTimeFormat(undefined, { + dateStyle: "medium", + timeStyle: "medium", + }); +}; + +const formatDateTime = (date: Date, locale?: Intl.Locale, timeZone?: string) => { + let dateTimeFormatter; + try { + dateTimeFormatter = new Intl.DateTimeFormat(locale, { + dateStyle, + timeStyle, + timeZone, + }); + } catch (error) { + if ((error as Error).message.includes("invalid time zone")) { + try { + dateTimeFormatter = new Intl.DateTimeFormat(locale, { + dateStyle, + timeStyle, + }); + } catch { + dateTimeFormatter = createBasicDateTimeFormatter(); + } + } else if ((error as Error).message.includes("invalid language tag")) { + try { + dateTimeFormatter = new Intl.DateTimeFormat(undefined, { + dateStyle, + timeStyle, + timeZone, + }); + } catch { + dateTimeFormatter = createBasicDateTimeFormatter(); + } + } else { + dateTimeFormatter = createBasicDateTimeFormatter(); + } + } + + return dateTimeFormatter.format(date); +}; + export interface DateTimeProps { date: Date; + locale?: Intl.Locale; systemTimeZone?: string; } -export const DateTime = ({ date, systemTimeZone }: DateTimeProps) => { - console.log(systemTimeZone); +export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { + console.log(systemTimeZone, locale); const FORMAT = "YYYY-MM-DD hh:mm:ss"; @@ -69,7 +116,11 @@ export const DateTime = ({ date, systemTimeZone }: DateTimeProps) => { let systemTime; if (systemTimeZone) { dayjs.extend(timezone); - systemTime = dayjsDate.tz(systemTimeZone); + try { + systemTime = dayjsDate.tz(systemTimeZone); + } catch { + systemTime = dayjsDate.tz("America/New_York"); + } } return ( @@ -82,8 +133,9 @@ export const DateTime = ({ date, systemTimeZone }: DateTimeProps) => { orientation="vertical" padding="none" > - + {date.getTime()} + {date.toISOString()} { justifyContent="space-between" > UTC: - {dayjsDate.utc().format(FORMAT)} + + {formatDateTime(dayjsDate.utc().toDate(), locale, "UTC")} + Local: - {dayjsDate.format(FORMAT)} + {formatDateTime(dayjsDate.toDate(), locale)} {systemTime && ( System: - {systemTime.format(FORMAT)} + + {formatDateTime(systemTime.toDate(), locale, systemTimeZone)} + )} From d07a2d6827bc68a8d5496b29b8e95cb3dfcbcdb1 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Wed, 31 Jul 2024 10:50:07 -0400 Subject: [PATCH 04/15] add local and system timezone --- src/components/DateTime/DateTime.stories.tsx | 2 +- src/components/DateTime/DateTime.tsx | 25 +++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/DateTime/DateTime.stories.tsx b/src/components/DateTime/DateTime.stories.tsx index 5becc492..fe760366 100644 --- a/src/components/DateTime/DateTime.stories.tsx +++ b/src/components/DateTime/DateTime.stories.tsx @@ -9,7 +9,7 @@ export default { export const Playground = { args: { date: new Date(), - locale: 'en-US', + locale: "en-US", systemTimeZone: "America/Los_Angeles", title: "DateTime", }, diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index 9341180c..b95c1f24 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -6,6 +6,12 @@ import timezone from "dayjs/plugin/timezone"; import updateLocale from "dayjs/plugin/updateLocale"; import utc from "dayjs/plugin/utc"; +import { Container } from "@/components/Container/Container"; +import { Panel } from "@/components/Panel/Panel"; +import { Popover } from "@/components/Popover/Popover"; +import { Text } from "@/components/Typography/Text/Text"; +import styled from "styled-components"; + dayjs.extend(duration); dayjs.extend(localizedFormat); dayjs.extend(updateLocale); @@ -49,10 +55,9 @@ dayjs.updateLocale("en", { }, }); -import { Container } from "../Container/Container"; -import { Panel } from "../Panel/Panel"; -import { Popover } from "../Popover/Popover"; -import { Text } from "../Typography/Text/Text"; +const UnderlinedTrigger = styled(Popover.Trigger)` + text-decoration: wavy underline; +`; const dateStyle = "medium"; const timeStyle = "medium"; @@ -109,8 +114,6 @@ export interface DateTimeProps { export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { console.log(systemTimeZone, locale); - const FORMAT = "YYYY-MM-DD hh:mm:ss"; - const dayjsDate = dayjs(date); let systemTime; @@ -125,16 +128,16 @@ export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { return ( - + {dayjs.utc(date).fromNow()} - + - {date.getTime()} + {Math.round(date.getTime() / 1000)} {date.toISOString()} @@ -151,7 +154,7 @@ export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { orientation="horizontal" justifyContent="space-between" > - Local: + Local ({dayjs.tz.guess()}): {formatDateTime(dayjsDate.toDate(), locale)} {systemTime && ( @@ -160,7 +163,7 @@ export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { justifyContent="space-between" minWidth="260px" > - System: + System ({systemTimeZone}): {formatDateTime(systemTime.toDate(), locale, systemTimeZone)} From 37fa21b6eafb162d0dd6f11a97820f7e68ff5726 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Mon, 5 Aug 2024 11:57:53 -0400 Subject: [PATCH 05/15] make stories eaiser to use --- src/components/DateTime/DateTime.stories.tsx | 24 ++++++++++++++++++++ src/components/DateTime/DateTime.tsx | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/components/DateTime/DateTime.stories.tsx b/src/components/DateTime/DateTime.stories.tsx index fe760366..4b8434df 100644 --- a/src/components/DateTime/DateTime.stories.tsx +++ b/src/components/DateTime/DateTime.stories.tsx @@ -1,6 +1,30 @@ import { DateTime } from "./DateTime"; export default { + argTypes: { + locale: { + options: ["en-US", "en-GB", "fr-FR", "de-DE", "ru-RU"], + control: { + type: "select", + }, + }, + systemTimeZone: { + options: [ + "America/Denver", + "America/Los_Angeles", + "America/New_York", + "Asia/Shanghai", + "Asia/Tokyo", + "Europe/London", + "Europe/Berlin", + "Europe/Moscow", + "Europe/Rome", + ], + control: { + type: "select", + }, + }, + }, component: DateTime, title: "Display/DateTime", tags: ["autodocs"], diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index b95c1f24..1b2f5dd7 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -64,8 +64,8 @@ const timeStyle = "medium"; const createBasicDateTimeFormatter = () => { return new Intl.DateTimeFormat(undefined, { - dateStyle: "medium", - timeStyle: "medium", + dateStyle, + timeStyle, }); }; From f8cc15c17accf0b6cf725f1911bba605799394ba Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 9 Aug 2024 10:48:05 -0400 Subject: [PATCH 06/15] Remove console.log statement --- src/components/DateTime/DateTime.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index 1b2f5dd7..ef5d6a72 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -112,8 +112,6 @@ export interface DateTimeProps { } export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { - console.log(systemTimeZone, locale); - const dayjsDate = dayjs(date); let systemTime; From 7eba4a0f72779033f06e0e0c9fe987cac5ba442e Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 9 Aug 2024 12:19:24 -0400 Subject: [PATCH 07/15] Add tests --- src/components/DateTime/DateTime.test.tsx | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/components/DateTime/DateTime.test.tsx diff --git a/src/components/DateTime/DateTime.test.tsx b/src/components/DateTime/DateTime.test.tsx new file mode 100644 index 00000000..eb9de62c --- /dev/null +++ b/src/components/DateTime/DateTime.test.tsx @@ -0,0 +1,60 @@ +import { DateTime } from "@/components/DateTime/DateTime"; +import { renderCUI } from "@/utils/test-utils"; +import { fireEvent } from "@testing-library/dom"; + +describe("DateTime", () => { + const baseDate = new Date("2024-07-04 11:45:00 AM"); + const systemTimeZone = "America/Los_Angeles"; + const locale = new Intl.Locale("en", { region: "US" }); + const actualTZ = process.env.TZ; + + beforeAll(() => { + global.ResizeObserver = vi.fn(() => { + return { + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), + }; + }); + + process.env.TZ = "America/New_York"; + }); + + afterAll(() => { + process.env.TZ = actualTZ; + }); + + beforeEach(() => { + vi.setSystemTime(baseDate); + }); + + it("renders the DateTime component with relevant timezone information", () => { + const fiveMinutesAgo = new Date("2024-07-04 11:40:00 AM"); + + const { getByText } = renderCUI( + + ); + + const trigger = getByText("5 minutes ago"); + expect(trigger).toBeInTheDocument(); + + fireEvent.click(trigger); + expect( + getByText(content => { + return content.startsWith("Local (America/New_York"); + }) + ).toBeInTheDocument(); + expect( + getByText(content => { + return content.startsWith("System (America/Los_Angeles)"); + }) + ).toBeInTheDocument(); + expect(getByText("Jul 4, 2024, 3:40:00 PM")).toBeInTheDocument(); + expect(getByText("Jul 4, 2024, 11:40:00 AM")).toBeInTheDocument(); + expect(getByText("Jul 4, 2024, 8:40:00 AM")).toBeInTheDocument(); + }); +}); From c549b8acbf23c00e86c03d369b1d9a4fdda93cdb Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 9 Aug 2024 12:19:34 -0400 Subject: [PATCH 08/15] Export DateTime and alphabetize list --- src/components/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/index.ts b/src/components/index.ts index 84933dbe..15fb55e1 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -14,22 +14,23 @@ export { Badge } from "./Badge/Badge"; export { BigStat } from "./BigStat/BigStat"; export { ButtonGroup } from "./ButtonGroup/ButtonGroup"; export { Button } from "./Button/Button"; -export { CardSecondary } from "./CardSecondary/CardSecondary"; -export { CardPrimary } from "./CardPrimary/CardPrimary"; export { CardHorizontal } from "./CardHorizontal/CardHorizontal"; +export { CardPrimary } from "./CardPrimary/CardPrimary"; export { CardPromotion } from "./CardPromotion/CardPromotion"; +export { CardSecondary } from "./CardSecondary/CardSecondary"; export { Checkbox } from "./Checkbox/Checkbox"; export { CodeBlock } from "./CodeBlock/CodeBlock"; +export { ConfirmationDialog } from "./ConfirmationDialog/ConfirmationDialog"; +export { ContextMenu } from "./ContextMenu/ContextMenu"; export { Container } from "./Container/Container"; export { DatePicker } from "./DatePicker/DatePicker"; +export { DateTime } from "@/components/DateTime/DateTime"; export { Dialog } from "./Dialog/Dialog"; -export { ConfirmationDialog } from "./ConfirmationDialog/ConfirmationDialog"; export { EllipsisContent } from "./EllipsisContent/EllipsisContent"; export { Flyout } from "./Flyout/Flyout"; export { FormContainer } from "./FormContainer/FormContainer"; export { GridContainer } from "./GridContainer/GridContainer"; export { InlineCodeBlock } from "./CodeBlock/InlineCodeBlock"; -export { ContextMenu } from "./ContextMenu/ContextMenu"; export { default as Flags } from "./icons/Flags"; export { Grid } from "./Grid/Grid"; export { HoverCard } from "./HoverCard/HoverCard"; From e187c2d1d8d2a87b59d078e941ab8d1fd937b4d0 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 9 Aug 2024 12:23:27 -0400 Subject: [PATCH 09/15] Tweak test to see if it works on CI --- src/components/DateTime/DateTime.test.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/DateTime/DateTime.test.tsx b/src/components/DateTime/DateTime.test.tsx index eb9de62c..0d282002 100644 --- a/src/components/DateTime/DateTime.test.tsx +++ b/src/components/DateTime/DateTime.test.tsx @@ -3,9 +3,6 @@ import { renderCUI } from "@/utils/test-utils"; import { fireEvent } from "@testing-library/dom"; describe("DateTime", () => { - const baseDate = new Date("2024-07-04 11:45:00 AM"); - const systemTimeZone = "America/Los_Angeles"; - const locale = new Intl.Locale("en", { region: "US" }); const actualTZ = process.env.TZ; beforeAll(() => { @@ -24,11 +21,12 @@ describe("DateTime", () => { process.env.TZ = actualTZ; }); - beforeEach(() => { + it("renders the DateTime component with relevant timezone information", () => { + const baseDate = new Date("2024-07-04 11:45:00 AM"); + const systemTimeZone = "America/Los_Angeles"; + const locale = new Intl.Locale("en", { region: "US" }); vi.setSystemTime(baseDate); - }); - it("renders the DateTime component with relevant timezone information", () => { const fiveMinutesAgo = new Date("2024-07-04 11:40:00 AM"); const { getByText } = renderCUI( From fe066d7a34fdff00bbb5dd93b5bde10f9663acc5 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 9 Aug 2024 12:28:27 -0400 Subject: [PATCH 10/15] Add two more expectations to tests --- src/components/DateTime/DateTime.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/DateTime/DateTime.test.tsx b/src/components/DateTime/DateTime.test.tsx index 0d282002..669f5c79 100644 --- a/src/components/DateTime/DateTime.test.tsx +++ b/src/components/DateTime/DateTime.test.tsx @@ -54,5 +54,7 @@ describe("DateTime", () => { expect(getByText("Jul 4, 2024, 3:40:00 PM")).toBeInTheDocument(); expect(getByText("Jul 4, 2024, 11:40:00 AM")).toBeInTheDocument(); expect(getByText("Jul 4, 2024, 8:40:00 AM")).toBeInTheDocument(); + expect(getByText(fiveMinutesAgo.getTime() / 1000)).toBeInTheDocument(); + expect(getByText(fiveMinutesAgo.toISOString())).toBeInTheDocument(); }); }); From 612bb783a741e77eaa16b35ffd828ed2d6b687f2 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 20 Dec 2024 14:40:49 -0500 Subject: [PATCH 11/15] make dayjs a peer dep --- package-lock.json | 7 +++++++ package.json | 2 +- src/components/DateTime/DateTime.tsx | 2 +- vite.config.ts | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09d78853..31a83f61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "watch": "^1.0.2" }, "peerDependencies": { + "dayjs": "^1.11.13", "react": "^18.2.0", "react-dom": "^18.2.0", "styled-components": ">= 5" @@ -9927,6 +9928,12 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "peer": true + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", diff --git a/package.json b/package.json index cbcd504c..89d11f28 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "dayjs": "^1.11.12", "lodash": "^4.17.21", "react-sortablejs": "^6.1.4", "react-syntax-highlighter": "^15.5.0", @@ -116,6 +115,7 @@ "watch": "^1.0.2" }, "peerDependencies": { + "dayjs": "^1.11.13", "react": "^18.2.0", "react-dom": "^18.2.0", "styled-components": ">= 5" diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index ef5d6a72..d2d841ad 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -10,7 +10,7 @@ import { Container } from "@/components/Container/Container"; import { Panel } from "@/components/Panel/Panel"; import { Popover } from "@/components/Popover/Popover"; import { Text } from "@/components/Typography/Text/Text"; -import styled from "styled-components"; +import { styled } from "styled-components"; dayjs.extend(duration); dayjs.extend(localizedFormat); diff --git a/vite.config.ts b/vite.config.ts index 938ae4de..6a3426d6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -40,6 +40,7 @@ export default defineConfig({ rollupOptions: { // Add _all_ external dependencies here external: [ + "dayjs", "react", "react-dom", "styled-components", @@ -51,6 +52,7 @@ export default defineConfig({ ], output: { globals: { + dayjs: "dayjs", react: "React", "styled-components": "styled", "react-dom": "ReactDOM", From 3b44ef048d695e7e049ae928d442b00741f03459 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 20 Dec 2024 16:10:14 -0500 Subject: [PATCH 12/15] made it closer match the designs --- src/components/DateTime/DateTime.stories.tsx | 7 ++ src/components/DateTime/DateTime.tsx | 99 +++++++++++--------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/components/DateTime/DateTime.stories.tsx b/src/components/DateTime/DateTime.stories.tsx index 4b8434df..71331f36 100644 --- a/src/components/DateTime/DateTime.stories.tsx +++ b/src/components/DateTime/DateTime.stories.tsx @@ -8,6 +8,12 @@ export default { type: "select", }, }, + side: { + control: { + type: "select", + }, + options: ["top", "right", "left", "bottom"], + }, systemTimeZone: { options: [ "America/Denver", @@ -34,6 +40,7 @@ export const Playground = { args: { date: new Date(), locale: "en-US", + side: "top", systemTimeZone: "America/Los_Angeles", title: "DateTime", }, diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateTime/DateTime.tsx index d2d841ad..7de5a31b 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateTime/DateTime.tsx @@ -1,17 +1,20 @@ import dayjs from "dayjs"; +import advancedFormat from "dayjs/plugin/advancedFormat"; import duration from "dayjs/plugin/duration"; import localizedFormat from "dayjs/plugin/localizedFormat"; import relativeTime from "dayjs/plugin/relativeTime"; import timezone from "dayjs/plugin/timezone"; import updateLocale from "dayjs/plugin/updateLocale"; import utc from "dayjs/plugin/utc"; +import { styled } from "styled-components"; -import { Container } from "@/components/Container/Container"; -import { Panel } from "@/components/Panel/Panel"; import { Popover } from "@/components/Popover/Popover"; import { Text } from "@/components/Typography/Text/Text"; -import { styled } from "styled-components"; +import { linkStyles, StyledLinkProps } from "@/components/Link/common"; +import { GridContainer } from "@/components/GridContainer/GridContainer"; +import { Container } from "@/components/Container/Container"; +dayjs.extend(advancedFormat); dayjs.extend(duration); dayjs.extend(localizedFormat); dayjs.extend(updateLocale); @@ -55,8 +58,8 @@ dayjs.updateLocale("en", { }, }); -const UnderlinedTrigger = styled(Popover.Trigger)` - text-decoration: wavy underline; +const UnderlinedTrigger = styled(Popover.Trigger)` + ${linkStyles} `; const dateStyle = "medium"; @@ -105,13 +108,21 @@ const formatDateTime = (date: Date, locale?: Intl.Locale, timeZone?: string) => return dateTimeFormatter.format(date); }; +export type ArrowPosition = "top" | "right" | "left" | "bottom"; + export interface DateTimeProps { date: Date; locale?: Intl.Locale; + side?: ArrowPosition; systemTimeZone?: string; } -export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { +export const DateTime = ({ + date, + locale, + side = "top", + systemTimeZone, +}: DateTimeProps) => { const dayjsDate = dayjs(date); let systemTime; @@ -126,49 +137,53 @@ export const DateTime = ({ date, locale, systemTimeZone }: DateTimeProps) => { return ( - + {dayjs.utc(date).fromNow()} - - + - - {Math.round(date.getTime() / 1000)} - {date.toISOString()} + Local + + + {formatDateTime(dayjsDate.toDate(), locale)} ({dayjsDate.format("z")}) + - - - UTC: - - {formatDateTime(dayjsDate.utc().toDate(), locale, "UTC")} - - - - Local ({dayjs.tz.guess()}): - {formatDateTime(dayjsDate.toDate(), locale)} - - {systemTime && ( - - System ({systemTimeZone}): + + {systemTime && ( + <> + System + + - {formatDateTime(systemTime.toDate(), locale, systemTimeZone)} + {formatDateTime(systemTime.toDate(), locale, systemTimeZone)} ( + {systemTime.format("z")}) - )} - - + + )} + + UTC + + + {formatDateTime(dayjsDate.utc().toDate(), locale, "UTC")} + + + + Unix + + {Math.round(date.getTime() / 1000)} + + ); From 500e3aa2fcecaef94da5d3284e4cb7ca56c63e71 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Mon, 6 Jan 2025 16:47:55 -0500 Subject: [PATCH 13/15] Rename to DateDetails --- .../DateDetails.stories.tsx} | 8 +- .../DateDetails/DateDetails.test.tsx | 95 +++++++++++++++++++ .../DateDetails.tsx} | 32 +++---- src/components/DateTime/DateTime.test.tsx | 60 ------------ src/components/index.ts | 2 +- 5 files changed, 116 insertions(+), 81 deletions(-) rename src/components/{DateTime/DateTime.stories.tsx => DateDetails/DateDetails.stories.tsx} (86%) create mode 100644 src/components/DateDetails/DateDetails.test.tsx rename src/components/{DateTime/DateTime.tsx => DateDetails/DateDetails.tsx} (81%) delete mode 100644 src/components/DateTime/DateTime.test.tsx diff --git a/src/components/DateTime/DateTime.stories.tsx b/src/components/DateDetails/DateDetails.stories.tsx similarity index 86% rename from src/components/DateTime/DateTime.stories.tsx rename to src/components/DateDetails/DateDetails.stories.tsx index 71331f36..c2cc647d 100644 --- a/src/components/DateTime/DateTime.stories.tsx +++ b/src/components/DateDetails/DateDetails.stories.tsx @@ -1,4 +1,4 @@ -import { DateTime } from "./DateTime"; +import { DateDetails } from "./DateDetails"; export default { argTypes: { @@ -31,8 +31,8 @@ export default { }, }, }, - component: DateTime, - title: "Display/DateTime", + component: DateDetails, + title: "Display/DateDetails", tags: ["autodocs"], }; @@ -42,6 +42,6 @@ export const Playground = { locale: "en-US", side: "top", systemTimeZone: "America/Los_Angeles", - title: "DateTime", + title: "DateDetails", }, }; diff --git a/src/components/DateDetails/DateDetails.test.tsx b/src/components/DateDetails/DateDetails.test.tsx new file mode 100644 index 00000000..7371b863 --- /dev/null +++ b/src/components/DateDetails/DateDetails.test.tsx @@ -0,0 +1,95 @@ +import { DateDetails } from "@/components/DateDetails/DateDetails"; +import { renderCUI } from "@/utils/test-utils"; +import { fireEvent } from "@testing-library/dom"; + +describe("DateDetails", () => { + const actualTZ = process.env.TZ; + + beforeAll(() => { + global.ResizeObserver = vi.fn(() => { + return { + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), + }; + }); + + process.env.TZ = "America/New_York"; + }); + + afterAll(() => { + process.env.TZ = actualTZ; + }); + + it("renders the DateDetails component with relevant timezone information", () => { + const baseDate = new Date("2024-12-24 11:45:00 AM"); + const systemTimeZone = "America/Los_Angeles"; + const locale = new Intl.Locale("en", { region: "US" }); + vi.setSystemTime(baseDate); + + const fiveMinutesAgo = new Date("2024-12-24 11:40:00 AM"); + + const { getByText } = renderCUI( + + ); + + const trigger = getByText("5 minutes ago"); + expect(trigger).toBeInTheDocument(); + + fireEvent.click(trigger); + expect( + getByText(content => { + return content.includes("EST"); + }) + ).toBeInTheDocument(); + expect( + getByText(content => { + return content.includes("PST"); + }) + ).toBeInTheDocument(); + expect(getByText("Dec 24, 2024, 4:40:00 PM")).toBeInTheDocument(); + expect(getByText("Dec 24, 2024, 11:40:00 AM (EST)")).toBeInTheDocument(); + expect(getByText("Dec 24, 2024, 8:40:00 AM (PST)")).toBeInTheDocument(); + expect(getByText(fiveMinutesAgo.getTime() / 1000)).toBeInTheDocument(); + }); + + it("handles Daylight Savings Time", () => { + const baseDate = new Date("2024-07-04 11:45:00 AM"); + const systemTimeZone = "America/Los_Angeles"; + const locale = new Intl.Locale("en", { region: "US" }); + vi.setSystemTime(baseDate); + + const fiveMinutesAgo = new Date("2024-07-04 11:40:00 AM"); + + const { getByText } = renderCUI( + + ); + + const trigger = getByText("5 minutes ago"); + expect(trigger).toBeInTheDocument(); + + fireEvent.click(trigger); + expect( + getByText(content => { + return content.includes("EDT"); + }) + ).toBeInTheDocument(); + expect( + getByText(content => { + return content.includes("PDT"); + }) + ).toBeInTheDocument(); + expect(getByText("Jul 4, 2024, 3:40:00 PM")).toBeInTheDocument(); + expect(getByText("Jul 4, 2024, 11:40:00 AM (EDT)")).toBeInTheDocument(); + expect(getByText("Jul 4, 2024, 8:40:00 AM (PDT)")).toBeInTheDocument(); + expect(getByText(fiveMinutesAgo.getTime() / 1000)).toBeInTheDocument(); + }); +}); diff --git a/src/components/DateTime/DateTime.tsx b/src/components/DateDetails/DateDetails.tsx similarity index 81% rename from src/components/DateTime/DateTime.tsx rename to src/components/DateDetails/DateDetails.tsx index 7de5a31b..dce863ea 100644 --- a/src/components/DateTime/DateTime.tsx +++ b/src/components/DateDetails/DateDetails.tsx @@ -65,17 +65,17 @@ const UnderlinedTrigger = styled(Popover.Trigger)` const dateStyle = "medium"; const timeStyle = "medium"; -const createBasicDateTimeFormatter = () => { +const createBasicDateDetailsFormatter = () => { return new Intl.DateTimeFormat(undefined, { dateStyle, timeStyle, }); }; -const formatDateTime = (date: Date, locale?: Intl.Locale, timeZone?: string) => { - let dateTimeFormatter; +const formatDateDetails = (date: Date, locale?: Intl.Locale, timeZone?: string) => { + let dateDetailsFormatter; try { - dateTimeFormatter = new Intl.DateTimeFormat(locale, { + dateDetailsFormatter = new Intl.DateTimeFormat(locale, { dateStyle, timeStyle, timeZone, @@ -83,46 +83,46 @@ const formatDateTime = (date: Date, locale?: Intl.Locale, timeZone?: string) => } catch (error) { if ((error as Error).message.includes("invalid time zone")) { try { - dateTimeFormatter = new Intl.DateTimeFormat(locale, { + dateDetailsFormatter = new Intl.DateTimeFormat(locale, { dateStyle, timeStyle, }); } catch { - dateTimeFormatter = createBasicDateTimeFormatter(); + dateDetailsFormatter = createBasicDateDetailsFormatter(); } } else if ((error as Error).message.includes("invalid language tag")) { try { - dateTimeFormatter = new Intl.DateTimeFormat(undefined, { + dateDetailsFormatter = new Intl.DateTimeFormat(undefined, { dateStyle, timeStyle, timeZone, }); } catch { - dateTimeFormatter = createBasicDateTimeFormatter(); + dateDetailsFormatter = createBasicDateDetailsFormatter(); } } else { - dateTimeFormatter = createBasicDateTimeFormatter(); + dateDetailsFormatter = createBasicDateDetailsFormatter(); } } - return dateTimeFormatter.format(date); + return dateDetailsFormatter.format(date); }; export type ArrowPosition = "top" | "right" | "left" | "bottom"; -export interface DateTimeProps { +export interface DateDetailsProps { date: Date; locale?: Intl.Locale; side?: ArrowPosition; systemTimeZone?: string; } -export const DateTime = ({ +export const DateDetails = ({ date, locale, side = "top", systemTimeZone, -}: DateTimeProps) => { +}: DateDetailsProps) => { const dayjsDate = dayjs(date); let systemTime; @@ -155,7 +155,7 @@ export const DateTime = ({ Local - {formatDateTime(dayjsDate.toDate(), locale)} ({dayjsDate.format("z")}) + {formatDateDetails(dayjsDate.toDate(), locale)} ({dayjsDate.format("z")}) @@ -165,7 +165,7 @@ export const DateTime = ({ - {formatDateTime(systemTime.toDate(), locale, systemTimeZone)} ( + {formatDateDetails(systemTime.toDate(), locale, systemTimeZone)} ( {systemTime.format("z")}) @@ -175,7 +175,7 @@ export const DateTime = ({ UTC - {formatDateTime(dayjsDate.utc().toDate(), locale, "UTC")} + {formatDateDetails(dayjsDate.utc().toDate(), locale, "UTC")} diff --git a/src/components/DateTime/DateTime.test.tsx b/src/components/DateTime/DateTime.test.tsx deleted file mode 100644 index 669f5c79..00000000 --- a/src/components/DateTime/DateTime.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { DateTime } from "@/components/DateTime/DateTime"; -import { renderCUI } from "@/utils/test-utils"; -import { fireEvent } from "@testing-library/dom"; - -describe("DateTime", () => { - const actualTZ = process.env.TZ; - - beforeAll(() => { - global.ResizeObserver = vi.fn(() => { - return { - observe: vi.fn(), - unobserve: vi.fn(), - disconnect: vi.fn(), - }; - }); - - process.env.TZ = "America/New_York"; - }); - - afterAll(() => { - process.env.TZ = actualTZ; - }); - - it("renders the DateTime component with relevant timezone information", () => { - const baseDate = new Date("2024-07-04 11:45:00 AM"); - const systemTimeZone = "America/Los_Angeles"; - const locale = new Intl.Locale("en", { region: "US" }); - vi.setSystemTime(baseDate); - - const fiveMinutesAgo = new Date("2024-07-04 11:40:00 AM"); - - const { getByText } = renderCUI( - - ); - - const trigger = getByText("5 minutes ago"); - expect(trigger).toBeInTheDocument(); - - fireEvent.click(trigger); - expect( - getByText(content => { - return content.startsWith("Local (America/New_York"); - }) - ).toBeInTheDocument(); - expect( - getByText(content => { - return content.startsWith("System (America/Los_Angeles)"); - }) - ).toBeInTheDocument(); - expect(getByText("Jul 4, 2024, 3:40:00 PM")).toBeInTheDocument(); - expect(getByText("Jul 4, 2024, 11:40:00 AM")).toBeInTheDocument(); - expect(getByText("Jul 4, 2024, 8:40:00 AM")).toBeInTheDocument(); - expect(getByText(fiveMinutesAgo.getTime() / 1000)).toBeInTheDocument(); - expect(getByText(fiveMinutesAgo.toISOString())).toBeInTheDocument(); - }); -}); diff --git a/src/components/index.ts b/src/components/index.ts index 15fb55e1..6f4ecf98 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -23,8 +23,8 @@ export { CodeBlock } from "./CodeBlock/CodeBlock"; export { ConfirmationDialog } from "./ConfirmationDialog/ConfirmationDialog"; export { ContextMenu } from "./ContextMenu/ContextMenu"; export { Container } from "./Container/Container"; +export { DateDetails } from "@/components/DateDetails/DateDetails"; export { DatePicker } from "./DatePicker/DatePicker"; -export { DateTime } from "@/components/DateTime/DateTime"; export { Dialog } from "./Dialog/Dialog"; export { EllipsisContent } from "./EllipsisContent/EllipsisContent"; export { Flyout } from "./Flyout/Flyout"; From 7293a85ecf844ea42edb78c0a6b1db382ea5aa97 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Fri, 7 Feb 2025 16:00:43 -0500 Subject: [PATCH 14/15] match review feedback --- .../DateDetails/DateDetails.stories.tsx | 21 +++-- .../DateDetails/DateDetails.test.tsx | 40 ++++++--- src/components/DateDetails/DateDetails.tsx | 89 ++++++++----------- 3 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/components/DateDetails/DateDetails.stories.tsx b/src/components/DateDetails/DateDetails.stories.tsx index c2cc647d..5df2549b 100644 --- a/src/components/DateDetails/DateDetails.stories.tsx +++ b/src/components/DateDetails/DateDetails.stories.tsx @@ -1,19 +1,17 @@ +import { Args } from "@storybook/react"; import { DateDetails } from "./DateDetails"; export default { argTypes: { - locale: { - options: ["en-US", "en-GB", "fr-FR", "de-DE", "ru-RU"], - control: { - type: "select", - }, - }, side: { control: { type: "select", }, options: ["top", "right", "left", "bottom"], }, + date: { + control: "date", + }, systemTimeZone: { options: [ "America/Denver", @@ -39,9 +37,18 @@ export default { export const Playground = { args: { date: new Date(), - locale: "en-US", side: "top", systemTimeZone: "America/Los_Angeles", title: "DateDetails", }, + render: (args: Args) => { + const date = args.date ? new Date(args.date) : new Date(); + return ( + + ); + }, }; diff --git a/src/components/DateDetails/DateDetails.test.tsx b/src/components/DateDetails/DateDetails.test.tsx index 7371b863..8c337c47 100644 --- a/src/components/DateDetails/DateDetails.test.tsx +++ b/src/components/DateDetails/DateDetails.test.tsx @@ -24,7 +24,6 @@ describe("DateDetails", () => { it("renders the DateDetails component with relevant timezone information", () => { const baseDate = new Date("2024-12-24 11:45:00 AM"); const systemTimeZone = "America/Los_Angeles"; - const locale = new Intl.Locale("en", { region: "US" }); vi.setSystemTime(baseDate); const fiveMinutesAgo = new Date("2024-12-24 11:40:00 AM"); @@ -32,7 +31,6 @@ describe("DateDetails", () => { const { getByText } = renderCUI( ); @@ -51,16 +49,39 @@ describe("DateDetails", () => { return content.includes("PST"); }) ).toBeInTheDocument(); - expect(getByText("Dec 24, 2024, 4:40:00 PM")).toBeInTheDocument(); - expect(getByText("Dec 24, 2024, 11:40:00 AM (EST)")).toBeInTheDocument(); - expect(getByText("Dec 24, 2024, 8:40:00 AM (PST)")).toBeInTheDocument(); + expect(getByText("Dec 24, 4:40 p.m.")).toBeInTheDocument(); + expect(getByText("Dec 24, 11:40 a.m. (EST)")).toBeInTheDocument(); + expect(getByText("Dec 24, 8:40 a.m. (PST)")).toBeInTheDocument(); expect(getByText(fiveMinutesAgo.getTime() / 1000)).toBeInTheDocument(); }); + it("only shows the date if the previous date isn't in this year", () => { + const baseDate = new Date("2025-02-07 11:45:00 AM"); + const systemTimeZone = "America/Los_Angeles"; + vi.setSystemTime(baseDate); + + const oneYearAgo = new Date("2024-02-07 11:45:00 AM"); + + const { getByText } = renderCUI( + + ); + + const trigger = getByText("1 year ago"); + expect(trigger).toBeInTheDocument(); + + fireEvent.click(trigger); + expect(getByText("Feb 7, 2024, 4:45 p.m.")).toBeInTheDocument(); + expect(getByText("Feb 7, 2024, 11:45 a.m. (EST)")).toBeInTheDocument(); + expect(getByText("Feb 7, 2024, 8:45 a.m. (PST)")).toBeInTheDocument(); + expect(getByText(oneYearAgo.getTime() / 1000)).toBeInTheDocument(); + }); + it("handles Daylight Savings Time", () => { const baseDate = new Date("2024-07-04 11:45:00 AM"); const systemTimeZone = "America/Los_Angeles"; - const locale = new Intl.Locale("en", { region: "US" }); vi.setSystemTime(baseDate); const fiveMinutesAgo = new Date("2024-07-04 11:40:00 AM"); @@ -68,7 +89,6 @@ describe("DateDetails", () => { const { getByText } = renderCUI( ); @@ -87,9 +107,9 @@ describe("DateDetails", () => { return content.includes("PDT"); }) ).toBeInTheDocument(); - expect(getByText("Jul 4, 2024, 3:40:00 PM")).toBeInTheDocument(); - expect(getByText("Jul 4, 2024, 11:40:00 AM (EDT)")).toBeInTheDocument(); - expect(getByText("Jul 4, 2024, 8:40:00 AM (PDT)")).toBeInTheDocument(); + expect(getByText("Jul 4, 3:40 p.m.")).toBeInTheDocument(); + expect(getByText("Jul 4, 11:40 a.m. (EDT)")).toBeInTheDocument(); + expect(getByText("Jul 4, 8:40 a.m. (PDT)")).toBeInTheDocument(); expect(getByText(fiveMinutesAgo.getTime() / 1000)).toBeInTheDocument(); }); }); diff --git a/src/components/DateDetails/DateDetails.tsx b/src/components/DateDetails/DateDetails.tsx index dce863ea..6dc583b6 100644 --- a/src/components/DateDetails/DateDetails.tsx +++ b/src/components/DateDetails/DateDetails.tsx @@ -1,4 +1,4 @@ -import dayjs from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import advancedFormat from "dayjs/plugin/advancedFormat"; import duration from "dayjs/plugin/duration"; import localizedFormat from "dayjs/plugin/localizedFormat"; @@ -62,66 +62,56 @@ const UnderlinedTrigger = styled(Popover.Trigger)` ${linkStyles} `; -const dateStyle = "medium"; -const timeStyle = "medium"; +const formatDateDetails = (date: Dayjs, timezone?: string): string => { + const isCurrentYear = dayjs().year() === date.year(); + const formatForCurrentYear = "MMM D, h:mm a"; + const formatForPastYear = "MMM D, YYYY, h:mm a"; + + if (isCurrentYear) { + if (timezone) { + const dateWithTimezone = date.tz(timezone); + return dateWithTimezone + .format(formatForCurrentYear) + .replace("am", "a.m.") + .replace("pm", "p.m."); + } -const createBasicDateDetailsFormatter = () => { - return new Intl.DateTimeFormat(undefined, { - dateStyle, - timeStyle, - }); -}; + return date.format(formatForCurrentYear).replace("am", "a.m.").replace("pm", "p.m."); + } -const formatDateDetails = (date: Date, locale?: Intl.Locale, timeZone?: string) => { - let dateDetailsFormatter; - try { - dateDetailsFormatter = new Intl.DateTimeFormat(locale, { - dateStyle, - timeStyle, - timeZone, - }); - } catch (error) { - if ((error as Error).message.includes("invalid time zone")) { - try { - dateDetailsFormatter = new Intl.DateTimeFormat(locale, { - dateStyle, - timeStyle, - }); - } catch { - dateDetailsFormatter = createBasicDateDetailsFormatter(); - } - } else if ((error as Error).message.includes("invalid language tag")) { - try { - dateDetailsFormatter = new Intl.DateTimeFormat(undefined, { - dateStyle, - timeStyle, - timeZone, - }); - } catch { - dateDetailsFormatter = createBasicDateDetailsFormatter(); - } - } else { - dateDetailsFormatter = createBasicDateDetailsFormatter(); - } + if (timezone) { + const dateWithTimezone = date.tz(timezone); + return dateWithTimezone + .format(formatForPastYear) + .replace("am", "a.m.") + .replace("pm", "p.m."); } + return date.format(formatForPastYear).replace("am", "a.m.").replace("pm", "p.m."); +}; - return dateDetailsFormatter.format(date); +const formatTimezone = (date: Dayjs, timezone: string): string => { + return ( + new Intl.DateTimeFormat("en-US", { + timeZone: timezone, + timeZoneName: "short", + }) + .formatToParts(date.toDate()) + .find(part => part.type === "timeZoneName")?.value ?? date.format("z") + ); }; export type ArrowPosition = "top" | "right" | "left" | "bottom"; export interface DateDetailsProps { date: Date; - locale?: Intl.Locale; side?: ArrowPosition; systemTimeZone?: string; } export const DateDetails = ({ date, - locale, side = "top", - systemTimeZone, + systemTimeZone = "America/New_York", }: DateDetailsProps) => { const dayjsDate = dayjs(date); @@ -155,7 +145,8 @@ export const DateDetails = ({ Local - {formatDateDetails(dayjsDate.toDate(), locale)} ({dayjsDate.format("z")}) + {formatDateDetails(dayjsDate)} ( + {formatTimezone(dayjsDate, dayjs.tz.guess())}) @@ -165,8 +156,8 @@ export const DateDetails = ({ - {formatDateDetails(systemTime.toDate(), locale, systemTimeZone)} ( - {systemTime.format("z")}) + {formatDateDetails(systemTime, systemTimeZone)} ( + {formatTimezone(systemTime, systemTimeZone)}) @@ -174,9 +165,7 @@ export const DateDetails = ({ UTC - - {formatDateDetails(dayjsDate.utc().toDate(), locale, "UTC")} - + {formatDateDetails(dayjsDate.utc(), "UTC")} Unix From 78464f3a6adaa75cc48ff27731c16687a6911c27 Mon Sep 17 00:00:00 2001 From: Bucky Schwarz Date: Mon, 10 Feb 2025 12:09:06 -0500 Subject: [PATCH 15/15] Made text small, labels are muted --- src/components/DateDetails/DateDetails.tsx | 40 ++++++++++++++++------ 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/components/DateDetails/DateDetails.tsx b/src/components/DateDetails/DateDetails.tsx index 6dc583b6..a00736ab 100644 --- a/src/components/DateDetails/DateDetails.tsx +++ b/src/components/DateDetails/DateDetails.tsx @@ -91,7 +91,7 @@ const formatDateDetails = (date: Dayjs, timezone?: string): string => { const formatTimezone = (date: Dayjs, timezone: string): string => { return ( - new Intl.DateTimeFormat("en-US", { + new Intl.DateTimeFormat(undefined, { timeZone: timezone, timeZoneName: "short", }) @@ -128,7 +128,7 @@ export const DateDetails = ({ return ( {dayjs.utc(date).fromNow()} @@ -142,9 +142,14 @@ export const DateDetails = ({ gridTemplateColumns="repeat(2, auto)" gap="sm" > - Local + + Local + - + {formatDateDetails(dayjsDate)} ( {formatTimezone(dayjsDate, dayjs.tz.guess())}) @@ -152,10 +157,15 @@ export const DateDetails = ({ {systemTime && ( <> - System + + System + - + {formatDateDetails(systemTime, systemTimeZone)} ( {formatTimezone(systemTime, systemTimeZone)}) @@ -163,14 +173,24 @@ export const DateDetails = ({ )} - UTC + + UTC + - {formatDateDetails(dayjsDate.utc(), "UTC")} + {formatDateDetails(dayjsDate.utc(), "UTC")} - Unix + + Unix + - {Math.round(date.getTime() / 1000)} + {Math.round(date.getTime() / 1000)}