// 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 `