From d60e6309c29b412f81413a4baaa78bc59e21491f Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 7 Jan 2026 11:47:03 +0530 Subject: [PATCH 1/2] feat: add a tooltip for copying values in leaderboard --- .../leaderboard/LeaderboardCell.svelte | 58 ++++++++++ .../leaderboard/LeaderboardRow.svelte | 106 +++++++++--------- 2 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte new file mode 100644 index 00000000000..454b758a9f3 --- /dev/null +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte @@ -0,0 +1,58 @@ + + + + + + + + {#if clipboardSupported && !disabled} + +
+ Copy + {copyLabel} to clipboard +
+ + + Click + +
+ {/if} +
diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte index e71970d7bf4..1cc192605e5 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte @@ -19,6 +19,8 @@ deltaColumn, MEASURES_PADDING, } from "./leaderboard-widths"; + import LeaderboardCell from "@rilldata/web-common/features/dashboards/leaderboard/LeaderboardCell.svelte"; + import { builderActions, getAttrs } from "bits-ui"; export let itemData: LeaderboardItemData; export let dimensionName: string; @@ -211,59 +213,63 @@ selectionIndex={itemData?.selectedIndex} /> - shiftClickHandler(dimensionValue), - })} - on:pointerover={() => { - if (dimensionValue) { - // Always update the value in the store, but don't change visibility - cellInspectorStore.updateValue(dimensionValue.toString()); - } - }} - on:focus={() => { - if (dimensionValue) { - // Always update the value in the store, but don't change visibility - cellInspectorStore.updateValue(dimensionValue.toString()); - } - }} - class="relative size-full flex flex-none justify-between items-center leaderboard-label" - style:background={dimensionGradients} - > - - - - - {#if previousValueString && hovered} - - {previousValueString} → + + shiftClickHandler(dimensionValue), + })} + on:pointerover={() => { + if (dimensionValue) { + // Always update the value in the store, but don't change visibility + cellInspectorStore.updateValue(dimensionValue.toString()); + } + }} + on:focus={() => { + if (dimensionValue) { + // Always update the value in the store, but don't change visibility + cellInspectorStore.updateValue(dimensionValue.toString()); + } + }} + class="relative size-full flex flex-none justify-between items-center leaderboard-label" + style:background={dimensionGradients} + {...getAttrs([builder])} + use:builderActions={{ builders: [builder] }} + > + + - {/if} - {#if href} - - - - - - {/if} - + {previousValueString} → + + {/if} + + {#if href} + + + + + + {/if} + + {#each Object.keys(values) as measureName} Date: Thu, 8 Jan 2026 16:51:20 +0530 Subject: [PATCH 2/2] Replace wil LeaderboardCell --- .../leaderboard/LeaderboardCell.svelte | 94 ++++++-- .../leaderboard/LeaderboardRow.svelte | 221 ++++-------------- 2 files changed, 131 insertions(+), 184 deletions(-) diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte index 454b758a9f3..54c622aacef 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardCell.svelte @@ -3,16 +3,31 @@ import * as Tooltip from "@rilldata/web-common/components/tooltip-v2"; import Shortcut from "@rilldata/web-common/components/tooltip/Shortcut.svelte"; import StackingWord from "@rilldata/web-common/components/tooltip/StackingWord.svelte"; - import { isClipboardApiSupported } from "@rilldata/web-common/lib/actions/copy-to-clipboard.ts"; + import { + copyToClipboard, + isClipboardApiSupported, + } from "@rilldata/web-common/lib/actions/copy-to-clipboard.ts"; + import { builderActions, getAttrs } from "bits-ui"; + import { TOOLTIP_STRING_LIMIT } from "@rilldata/web-common/layout/config.ts"; + import { modified } from "@rilldata/web-common/lib/actions/modified-click.ts"; + import { cellInspectorStore } from "@rilldata/web-common/features/dashboards/stores/cell-inspector-store.ts"; - export let copyLabel = "this value"; - export let hideAfter = 3000; + export let value: string; + export let type: "dimension" | "measure" | "comparison"; + export let className: string = ""; + export let background: string = ""; + + const HideLeaderboardTooltipAfter = 3000; const clipboardSupported = typeof navigator !== "undefined" ? isClipboardApiSupported() : false; const disabled = !clipboardSupported; let tooltipActive = false; + $: if (tooltipActive) { + showTemporarily(); + } + let hideTimer: ReturnType | undefined; function clearHideTimer() { @@ -24,31 +39,60 @@ function showTemporarily() { if (disabled) return; - if (hideAfter > 0) { - clearHideTimer(); - hideTimer = setTimeout(() => { - tooltipActive = false; - }, hideAfter); - } + clearHideTimer(); + hideTimer = setTimeout(() => { + tooltipActive = false; + }, HideLeaderboardTooltipAfter); } - $: if (tooltipActive) { - showTemporarily(); + function shiftClickHandler(label: string) { + let truncatedLabel = label?.toString(); + if (truncatedLabel?.length > TOOLTIP_STRING_LIMIT) { + truncatedLabel = `${truncatedLabel.slice(0, TOOLTIP_STRING_LIMIT)}...`; + } + copyToClipboard( + label, + `copied dimension value "${truncatedLabel}" to clipboard`, + ); } onDestroy(clearHideTimer); - + - + shiftClickHandler(value), + })} + on:pointerover={() => { + if (value) { + // Always update the value in the store, but don't change visibility + cellInspectorStore.updateValue(value.toString()); + } + }} + on:focus={() => { + if (value) { + // Always update the value in the store, but don't change visibility + cellInspectorStore.updateValue(value.toString()); + } + }} + style:background + class="{type}-cell {className}" + > + + {#if clipboardSupported && !disabled}
Copy - {copyLabel} to clipboard + {value} to clipboard
+ Click @@ -56,3 +100,25 @@
{/if}
+ + diff --git a/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte b/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte index 1cc192605e5..23a4f8bc295 100644 --- a/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte +++ b/web-common/src/features/dashboards/leaderboard/LeaderboardRow.svelte @@ -2,14 +2,10 @@ import FormattedDataType from "@rilldata/web-common/components/data-types/FormattedDataType.svelte"; import PercentageChange from "@rilldata/web-common/components/data-types/PercentageChange.svelte"; import ExternalLink from "@rilldata/web-common/components/icons/ExternalLink.svelte"; - import { TOOLTIP_STRING_LIMIT } from "@rilldata/web-common/layout/config"; - import { copyToClipboard } from "@rilldata/web-common/lib/actions/copy-to-clipboard"; - import { modified } from "@rilldata/web-common/lib/actions/modified-click"; import { clamp } from "@rilldata/web-common/lib/clamp"; import { formatMeasurePercentageDifference } from "@rilldata/web-common/lib/number-formatting/percentage-formatter"; import { slide } from "svelte/transition"; import { type LeaderboardItemData, makeHref } from "./leaderboard-utils"; - import { cellInspectorStore } from "../stores/cell-inspector-store"; import LeaderboardItemFilterIcon from "./LeaderboardItemFilterIcon.svelte"; import LongBarZigZag from "./LongBarZigZag.svelte"; import { @@ -20,7 +16,6 @@ MEASURES_PADDING, } from "./leaderboard-widths"; import LeaderboardCell from "@rilldata/web-common/features/dashboards/leaderboard/LeaderboardCell.svelte"; - import { builderActions, getAttrs } from "bits-ui"; export let itemData: LeaderboardItemData; export let dimensionName: string; @@ -162,16 +157,9 @@ }), ); - function shiftClickHandler(label: string) { - let truncatedLabel = label?.toString(); - if (truncatedLabel?.length > TOOLTIP_STRING_LIMIT) { - truncatedLabel = `${truncatedLabel.slice(0, TOOLTIP_STRING_LIMIT)}...`; - } - copyToClipboard( - label, - `copied dimension value "${truncatedLabel}" to clipboard`, - ); - } + $: dimensionCellClass = `relative size-full flex flex-none justify-between items-center leaderboard-label ${ + atLeastOneActive ? "cursor-pointer" : "" + } ${excluded ? "ui-copy-disabled" : ""} ${!excluded && selected ? "ui-copy-strong" : ""}`; function onDimensionCellClick(e: MouseEvent) { // Check if user has selected text @@ -213,87 +201,48 @@ selectionIndex={itemData?.selectedIndex} /> - - shiftClickHandler(dimensionValue), - })} - on:pointerover={() => { - if (dimensionValue) { - // Always update the value in the store, but don't change visibility - cellInspectorStore.updateValue(dimensionValue.toString()); - } - }} - on:focus={() => { - if (dimensionValue) { - // Always update the value in the store, but don't change visibility - cellInspectorStore.updateValue(dimensionValue.toString()); - } - }} - class="relative size-full flex flex-none justify-between items-center leaderboard-label" - style:background={dimensionGradients} - {...getAttrs([builder])} - use:builderActions={{ builders: [builder] }} - > - - + + + + + + {#if previousValueString && hovered} + + {previousValueString} → + {/if} - {#if previousValueString && hovered} - + - {previousValueString} → - - {/if} - - {#if href} - - - - - - {/if} - + + + + {/if} {#each Object.keys(values) as measureName} - shiftClickHandler(values[measureName]?.toString() || ""), - })} - style:background={leaderboardMeasureNames.length === 1 + { - const value = values[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} - on:focus={() => { - const value = values[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} >
{/if} - + {#if isValidPercentOfTotal(measureName) && shouldShowContextColumns(measureName)} - - shiftClickHandler(pctOfTotals[measureName]?.toString() || ""), - })} - on:pointerover={() => { - const value = pctOfTotals[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} - on:focus={() => { - const value = pctOfTotals[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} + {/if} - + {/if} {#if isTimeComparisonActive && shouldShowContextColumns(measureName)} - - shiftClickHandler(deltaAbsMap[measureName]?.toString() || ""), - })} - on:pointerover={() => { - const value = deltaAbsMap[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} - on:focus={() => { - const value = deltaAbsMap[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} + - + {/if} {#if isTimeComparisonActive && shouldShowContextColumns(measureName)} - - shiftClickHandler(deltaRels[measureName]?.toString() || ""), - })} - on:pointerover={() => { - const value = deltaRels[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} - on:focus={() => { - const value = deltaRels[measureName]?.toString() || ""; - if (value) { - cellInspectorStore.updateValue(value); - } - }} + {/if} - + {/if} {/each}