diff --git a/frontend/__snapshots__/scenes-app-recordings--recordings-play-list-no-pinned-recordings.png b/frontend/__snapshots__/scenes-app-recordings--recordings-play-list-no-pinned-recordings.png index b79c4b316f022..0dd5b12b4fe35 100644 Binary files a/frontend/__snapshots__/scenes-app-recordings--recordings-play-list-no-pinned-recordings.png and b/frontend/__snapshots__/scenes-app-recordings--recordings-play-list-no-pinned-recordings.png differ diff --git a/frontend/src/lib/components/TZLabel/index.scss b/frontend/src/lib/components/TZLabel/index.scss index d987e42d411e5..cdfb2645b50d6 100644 --- a/frontend/src/lib/components/TZLabel/index.scss +++ b/frontend/src/lib/components/TZLabel/index.scss @@ -1,38 +1,27 @@ -.tz-label { - white-space: nowrap; +.TZLabelPopover { + .TZLabelPopover__row { + display: flex; + margin-top: 0.5rem; - &.tz-label--hoverable { - border-bottom: 1px dotted var(--border-bold); - cursor: default; - } -} + > :nth-child(1) { + font-weight: bold; + color: var(--primary-alt); + margin-right: 6px; + } -.tz-label-popover { - .divider { - margin-right: -4px; - margin-left: -4px; - border-bottom: 1px solid var(--border-light); - } - .timezones { - .timezone { - display: flex; - margin-top: 8px; - .name { - font-weight: bold; - color: var(--primary-alt); - margin-right: 6px; - } + > :nth-child(2) { + color: var(--muted); + margin-right: 16px; + flex-grow: 1; + font-style: italic; + } - .scope { - color: var(--muted); - margin-right: 16px; - flex-grow: 1; - font-style: italic; - } + > :nth-child(3) { + min-width: 10rem; + text-align: right; - .time { - min-width: 170px; - text-align: right; + .TZLabelPopover--seconds & { + min-width: 12rem; } } } diff --git a/frontend/src/lib/components/TZLabel/index.tsx b/frontend/src/lib/components/TZLabel/index.tsx index 2f25a5d25bc18..a1d28b764d79e 100644 --- a/frontend/src/lib/components/TZLabel/index.tsx +++ b/frontend/src/lib/components/TZLabel/index.tsx @@ -1,32 +1,20 @@ import './index.scss' -import { Col, Popover, Row } from 'antd' +import { Popover } from 'antd' import { useActions, useValues } from 'kea' -import { ProjectOutlined, LaptopOutlined, GlobalOutlined, SettingOutlined } from '@ant-design/icons' -import { Link } from 'lib/lemon-ui/Link' +import { ProjectOutlined, LaptopOutlined, GlobalOutlined } from '@ant-design/icons' import { humanFriendlyDetailedTime, shortTimeZone } from 'lib/utils' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { teamLogic } from '../../../scenes/teamLogic' import { dayjs } from 'lib/dayjs' -import { usePeriodicRerender } from 'lib/hooks/usePeriodicRerender' import clsx from 'clsx' -import React from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { styles } from '../../../styles/vars' +import { LemonButton, LemonDivider } from '@posthog/lemon-ui' +import { IconSettings } from 'lib/lemon-ui/icons' +import { urls } from 'scenes/urls' const BASE_OUTPUT_FORMAT = 'ddd, MMM D, YYYY h:mm A' -function TZConversionHeader(): JSX.Element { - return ( -

- Timezone conversion - - - - - -

- ) -} - interface TZLabelRawProps { time: string | dayjs.Dayjs showSeconds?: boolean @@ -37,6 +25,60 @@ interface TZLabelRawProps { className?: string } +const TZLabelPopoverContent = React.memo(function TZLabelPopoverContent({ + showSeconds, + time, +}: Pick & { time: dayjs.Dayjs }): JSX.Element { + const DATE_OUTPUT_FORMAT = !showSeconds ? BASE_OUTPUT_FORMAT : `${BASE_OUTPUT_FORMAT}:ss` + const { currentTeam } = useValues(teamLogic) + const { reportTimezoneComponentViewed } = useActions(eventUsageLogic) + + useEffect(() => { + reportTimezoneComponentViewed('label', currentTeam?.timezone, shortTimeZone()) + }, []) + + return ( +
+
+

Timezone conversion

+ + } size="small" to={urls.projectSettings('timezone')} /> + +
+ + + +
+
+
+ {shortTimeZone(undefined, time.toDate())} +
+
Your device
+
{time.format(DATE_OUTPUT_FORMAT)}
+
+ {currentTeam && ( +
+
+ {shortTimeZone(currentTeam.timezone, time.toDate())} +
+
Project
+
{time.tz(currentTeam.timezone).format(DATE_OUTPUT_FORMAT)}
+
+ )} + {currentTeam?.timezone !== 'UTC' && ( +
+
+ UTC +
+
+
{time.tz('UTC').format(DATE_OUTPUT_FORMAT)}
+
+ )} +
+
+ ) +}) + /** Return a simple label component with timezone conversion UI. */ function TZLabelRaw({ time, @@ -47,73 +89,42 @@ function TZLabelRaw({ noStyles = false, className, }: TZLabelRawProps): JSX.Element { - usePeriodicRerender(1000) + const parsedTime = useMemo(() => (dayjs.isDayjs(time) ? time : dayjs(time)), [time]) - const parsedTime = dayjs.isDayjs(time) ? time : dayjs(time) - const { currentTeam } = useValues(teamLogic) + const format = useCallback(() => { + return formatDate || formatTime + ? humanFriendlyDetailedTime(parsedTime, formatDate, formatTime) + : parsedTime.fromNow() + }, [formatDate, formatTime, parsedTime]) - const DATE_OUTPUT_FORMAT = !showSeconds ? BASE_OUTPUT_FORMAT : `${BASE_OUTPUT_FORMAT}:ss` - const timeStyle = showSeconds ? { minWidth: 192 } : undefined + const [formattedContent, setFormattedContent] = useState(format()) - const { reportTimezoneComponentViewed } = useActions(eventUsageLogic) + useEffect(() => { + // NOTE: This is an optimization to make sure we don't needlessly re-render the component every second. + const interval = setInterval(() => { + if (format() !== formattedContent) { + setFormattedContent(format()) + } + }, 1000) + return () => clearInterval(interval) + }, [parsedTime, format]) const innerContent = ( - - {formatDate || formatTime - ? humanFriendlyDetailedTime(parsedTime, formatDate, formatTime) - : parsedTime.fromNow()} + + {formattedContent} ) if (showPopover) { - const handleVisibleChange = (visible: boolean): void => { - if (visible) { - reportTimezoneComponentViewed('label', currentTeam?.timezone, shortTimeZone()) - } - } - - const PopoverContent = ( -
- -
-
- - - {shortTimeZone(undefined, parsedTime.toDate())} - - Your device - - {parsedTime.format(DATE_OUTPUT_FORMAT)} - - - {currentTeam && ( - - - {shortTimeZone(currentTeam.timezone, parsedTime.toDate())} - - Project - - {parsedTime.tz(currentTeam.timezone).format(DATE_OUTPUT_FORMAT)} - - - )} - {currentTeam?.timezone !== 'UTC' && ( - - - UTC - - - - {parsedTime.tz('UTC').format(DATE_OUTPUT_FORMAT)} - - - )} -
-
- ) - return ( - + } + zIndex={styles.zPopover} + > {innerContent} ) diff --git a/playwright/e2e-vrt/layout/Navigation.spec.ts-snapshots/Navigation-App-Page-With-Side-Bar-Hidden-Mobile-1-chromium-linux.png b/playwright/e2e-vrt/layout/Navigation.spec.ts-snapshots/Navigation-App-Page-With-Side-Bar-Hidden-Mobile-1-chromium-linux.png index 86b5a55ad5b75..24af8b2279910 100644 Binary files a/playwright/e2e-vrt/layout/Navigation.spec.ts-snapshots/Navigation-App-Page-With-Side-Bar-Hidden-Mobile-1-chromium-linux.png and b/playwright/e2e-vrt/layout/Navigation.spec.ts-snapshots/Navigation-App-Page-With-Side-Bar-Hidden-Mobile-1-chromium-linux.png differ