// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file TimestampHoverCard.tsx * @input Uses React, HoverCard, IconButton, Icon, formatted lines * @output Default-exports the lazily-loaded copyable hover card for Timestamp * @position Split out of Timestamp so the overlay (HoverCard) and the copy * affordance's Icon/IconButton load only when a card is actually shown — the * default, card-less Timestamp never bundles this chunk. * * Layout: a semantic `
` laid out as a grid. Labelled cards use three * columns — label, value, and a trailing action column for copy buttons — * so the buttons align down a single column no matter how wide each value * is. The action column is only added when at least one row is copyable, so * a fully read-only card carries no dangling gutter. Label-less cards drop * the label column and stack to whatever columns remain. * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Timestamp/Timestamp.tsx (the lazy() + Suspense wrapper) * - /packages/core/src/Timestamp/Timestamp.doc.mjs (theming.targets) * - /packages/core/src/Timestamp/Timestamp.test.tsx */ import type { ReactNode } from 'react'; import { useCallback } from 'react'; import * as stylex from "../style-runtime.js"; import { HoverCard } from "../HoverCard/index.js"; import { IconButton } from "../IconButton/index.js"; import { Icon } from "../Icon/index.js"; import { useClipboard } from "../hooks/useClipboard.js"; import { useTranslator } from "../i18n/index.js"; import { themeProps } from "../utils/themeProps.js"; import "../theme/tokens.stylex.js"; import { colorVars, spacingVars, typeScaleVars, fontWeightVars } from "../theme/tokens.stylex.js"; import type { TimestampTooltipLine } from "./tooltipEntries.js"; const styles = { dl: { k1xSpc: "xrvj5dj", kGNEyG: "x6s0dn4", k1C7PZ: "xty4p9g", khm7nJ: "x1x7z4sm", kogj98: "x1ghz6dp", kmVPX3: "x1717udv", $$css: true as const } }; // Grid templates, selected per-card by which columns are present. // - hasAnyLabel + hasAnyCopyable → label | value | action // - hasAnyLabel only → label | value // - hasAnyCopyable only → value | action // - neither → value const gridTemplates = { labelValueAction: { kumcoG: "x1g3yg12", $$css: true as const }, labelValue: { kumcoG: "x1pmbctz", $$css: true as const }, valueAction: { kumcoG: "x52fmzj", $$css: true as const }, value: { kumcoG: "x1y6fwsi", $$css: true as const } }; /** How long the copied checkmark stays before reverting to the copy icon. */ const COPY_FEEDBACK_MS = 1500; /** * The per-row copy affordance: a compact ghost `IconButton` that writes the * row's value to the clipboard, flips `copy` → `check` for a moment, and * announces the copy to a polite live region (a swapped aria-label alone * isn't reliably announced). A clipboard rejection is a silent no-op. The * clipboard write, copied flag, reset timer, and announcement are owned by * `useClipboard` — shared with CodeBlock's built-in copy button. * * Kept as its own component so its state/timer/effect only exist for rows that * actually opt into copying — read-only rows render no button and carry none * of this machinery. */ function CopyButton({ value }: { value: string; }) { const t = useTranslator(); const { copy, isCopied: copied } = useClipboard({ announce: t('@astryx.timestamp.copied'), resetAfterMs: COPY_FEEDBACK_MS }); const handleCopy = useCallback(() => { void copy(value); }, [copy, value]); return } // Visible hover/focus hint via Button's built-in tooltip: 'Copy', // flipping to 'Copied' after a successful copy in step with the icon. // The full 'Copy ' string stays the aria-label for assistive tech. tooltip={t(copied ? '@astryx.timestamp.copied' : '@astryx.timestamp.copy')} label={copied ? t('@astryx.timestamp.copied') : t('@astryx.timestamp.copyValue', { value })} onClick={handleCopy} {...themeProps('timestamp-copy-button')} />; } /** * One row of the hover card: an optional label, the formatted instant, and — * when the card reserves an action column — a trailing action cell holding the * copy button (for a copyable row) or nothing (for a read-only row, so the * columns stay aligned). Pure presentation: the copy state lives in * {@link CopyButton}, which only copyable rows render. */ function EntryRow({ line, hasLabelColumn, hasActionColumn }: { line: TimestampTooltipLine; hasLabelColumn: boolean; hasActionColumn: boolean; }) { return
{hasLabelColumn &&
{line.label ?? ''}
}
{line.value}
{hasActionColumn &&
{line.isCopyable && }
}
; } export interface TimestampHoverCardProps { /** The rows to render, each a labelled instant, optionally copyable. */ lines: ReadonlyArray; /** Accessible name for the card. */ label: string; /** The anchor the card is attached to (the `