import * as React from 'react';
import { prettyLabel } from './prettyLabel';
import { cn } from '@/lib/utils';
// A single dimension filter: a whitelisted field key and the exact value to
// filter the view to. The field key must match the server-side whitelist
// (get_dimension_field_map() in inc/api/namespace.php).
export type DimensionDrill = { field: string, value: string };
// A drill target either sets the path filter directly (top content / URLs) or
// applies one or more dimension filters (referrer, country, tech, UTM, search).
// Multiple pairs compose into one drill — used by the UTM pair cards which set
// both source and campaign/medium at once. See AnalyticsView.onDrill.
export type DrillTarget = { path: string } | DimensionDrill | DimensionDrill[];
/**
* Share of the server's period total for a single row. Returns null when the
* total is unknown (0, missing, or still loading) so callers never render a
* misleading `0.0%` / `NaN%` — see #672.
*/
export function computeShare( value: number, total?: number ): number | null {
if ( ! total || total <= 0 ) {
return null;
}
return ( value / total ) * 100;
}
function formatShare( pct: number ): string {
// Matches Plausible's breakdown %: one decimal place (88.8%, 11.3%), two
// only for tiny shares below 0.1% (0.07%), and trailing zeros dropped so
// 8.0% renders as 8% and 0% as 0%. Number() does the trailing-zero strip.
const dp = pct < 0.1 ? 2 : 1;
return `${ Number( pct.toFixed( dp ) ) }%`;
}
/**
* Right-aligned value cell shared by the animated (Row) and static
* (CountryList) breakdown rows. Renders the count, then — only when a `total`
* is provided (count-based tiles, not engaged-time) — a collapsed percentage
* tray. The tray (and the matching `group/tray` on the list container) means
* hovering anywhere on the panel reveals the WHOLE % column at once: each
* tray animates open from the right, pushing the counts left to make room —
* the Plausible reveal. Resting state shows the count alone.
*/
export function ValueCell( {
value,
total,
children,
}: {
value: number;
total?: number;
children: React.ReactNode;
} ) {
const pct = computeShare( value, total );
return (
{ children }
{ pct !== null && (
{ formatShare( pct ) }
) }
);
}
/**
* Label overlaid on the bar — shared truncation/positioning contract. `key` is
* the raw value; `label` is the optional decoded display override.
*/
export function RowLabel( { value, label }: { value: string; label?: string } ) {
const text = label ?? prettyLabel( value );
return (
{ text }
);
}