Skip to content

Commit 1f939be

Browse files
committed
Reveal table header functions on hover
1 parent af739c5 commit 1f939be

File tree

1 file changed

+46
-25
lines changed

1 file changed

+46
-25
lines changed

apps/webapp/app/components/code/TSQLResultsTable.tsx

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1+
import { ChevronDownIcon, ChevronUpDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid";
12
import type { OutputColumnMetadata } from "@internal/clickhouse";
3+
import { IconFilter2 } from "@tabler/icons-react";
24
import { rankItem } from "@tanstack/match-sorter-utils";
35
import {
4-
useReactTable,
6+
flexRender,
57
getCoreRowModel,
68
getFilteredRowModel,
79
getSortedRowModel,
8-
flexRender,
9-
type ColumnDef,
10+
useReactTable,
1011
type CellContext,
11-
type ColumnResizeMode,
12+
type Column,
13+
type ColumnDef,
1214
type ColumnFiltersState,
15+
type ColumnResizeMode,
1316
type FilterFn,
14-
type Column,
15-
type SortingState,
1617
type SortDirection,
18+
type SortingState,
1719
} from "@tanstack/react-table";
1820
import { useVirtualizer } from "@tanstack/react-virtual";
1921
import { formatDurationMilliseconds, MachinePresetName } from "@trigger.dev/core/v3";
@@ -39,12 +41,6 @@ import { Paragraph } from "../primitives/Paragraph";
3941
import { TextLink } from "../primitives/TextLink";
4042
import { InfoIconTooltip, SimpleTooltip } from "../primitives/Tooltip";
4143
import { QueueName } from "../runs/v3/QueueName";
42-
import {
43-
FunnelIcon,
44-
ChevronUpIcon,
45-
ChevronDownIcon,
46-
ChevronUpDownIcon,
47-
} from "@heroicons/react/20/solid";
4844

4945
const MAX_STRING_DISPLAY_LENGTH = 64;
5046
const ROW_HEIGHT = 33; // Estimated row height in pixels
@@ -54,7 +50,7 @@ const MIN_COLUMN_WIDTH = 60;
5450
const MAX_COLUMN_WIDTH = 400;
5551
const CHAR_WIDTH_PX = 7.5; // Approximate width of a monospace character at text-xs (12px)
5652
const CELL_PADDING_PX = 40; // px-2 (8px) on each side + buffer for copy button
57-
const HEADER_ICONS_WIDTH_PX = 72; // Sort icon (16px) + filter icon (12px) + info icon (16px) + gaps (12px) + header padding (16px)
53+
const HEADER_ICONS_WIDTH_PX = 80; // Sort icon (16px) + filter icon (12px) + info icon (16px) + gaps (12px) + header padding (24px)
5854
const SAMPLE_SIZE = 100; // Number of rows to sample for width calculation
5955

6056
// Type for row data
@@ -768,6 +764,7 @@ function HeaderCellContent({
768764
sortDirection,
769765
onSortClick,
770766
canSort,
767+
isHeaderRowHovered,
771768
}: {
772769
alignment: "left" | "right";
773770
tooltip?: React.ReactNode;
@@ -778,19 +775,24 @@ function HeaderCellContent({
778775
sortDirection?: SortDirection | false;
779776
onSortClick?: (event: React.MouseEvent) => void;
780777
canSort?: boolean;
778+
isHeaderRowHovered?: boolean;
781779
}) {
782-
const [isHovered, setIsHovered] = useState(false);
780+
const [isCellHovered, setIsCellHovered] = useState(false);
781+
const [isFilterHovered, setIsFilterHovered] = useState(false);
782+
783+
const showIcons = isHeaderRowHovered || !!sortDirection || showFilters || hasActiveFilter;
784+
const sortHighlighted = isCellHovered && !isFilterHovered;
783785

784786
return (
785787
<div
786788
className={cn(
787-
"flex w-full items-center gap-1 overflow-hidden bg-background-bright py-2 pl-2 pr-1",
789+
"flex w-full items-center gap-1 overflow-hidden bg-background-bright py-2 pl-2 pr-3",
788790
"font-mono text-xs font-medium text-text-bright",
789791
alignment === "right" && "justify-end",
790792
canSort && "cursor-pointer select-none"
791793
)}
792-
onMouseEnter={() => setIsHovered(true)}
793-
onMouseLeave={() => setIsHovered(false)}
794+
onMouseEnter={() => setIsCellHovered(true)}
795+
onMouseLeave={() => setIsCellHovered(false)}
794796
onClick={onSortClick}
795797
>
796798
{tooltip ? (
@@ -800,19 +802,25 @@ function HeaderCellContent({
800802
})}
801803
>
802804
<span className="truncate text-left">{children}</span>
803-
<InfoIconTooltip
804-
content={tooltip}
805-
contentClassName="normal-case tracking-normal"
806-
enabled={isHovered}
807-
/>
805+
<span className="flex flex-shrink-0">
806+
<InfoIconTooltip
807+
content={tooltip}
808+
contentClassName="normal-case tracking-normal"
809+
enabled={isCellHovered}
810+
/>
811+
</span>
808812
</div>
809813
) : (
810814
<span className="min-w-0 flex-1 truncate text-left">{children}</span>
811815
)}
812816
{/* Sort indicator */}
813817
{canSort && (
814818
<span
815-
className={cn("flex-shrink-0", sortDirection ? "text-text-bright" : "text-text-dimmed")}
819+
className={cn(
820+
"flex-shrink-0 transition-all",
821+
showIcons ? "opacity-100" : "opacity-0",
822+
sortHighlighted ? "text-text-bright" : "text-text-dimmed"
823+
)}
816824
>
817825
{sortDirection === "asc" ? (
818826
<ChevronUpIcon className="size-4" />
@@ -829,10 +837,15 @@ function HeaderCellContent({
829837
e.stopPropagation();
830838
onFilterClick();
831839
}}
832-
className="flex-shrink-0 rounded text-text-dimmed transition-colors hover:bg-charcoal-700 hover:text-text-bright"
840+
onMouseEnter={() => setIsFilterHovered(true)}
841+
onMouseLeave={() => setIsFilterHovered(false)}
842+
className={cn(
843+
"flex-shrink-0 rounded text-text-dimmed transition-all hover:bg-charcoal-700 hover:text-text-bright",
844+
showIcons ? "opacity-100" : "opacity-0"
845+
)}
833846
title="Toggle column filters"
834847
>
835-
<FunnelIcon className="size-3" />
848+
<IconFilter2 className="size-4" />
836849
</button>
837850
)}
838851
</div>
@@ -900,6 +913,8 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
900913
const [focusFilterColumn, setFocusFilterColumn] = useState<string | null>(null);
901914
// State for column sorting
902915
const [sorting, setSorting] = useState<SortingState>(defaultSorting);
916+
// Track header row hover for showing sort/filter icons
917+
const [isHeaderRowHovered, setIsHeaderRowHovered] = useState(false);
903918

904919
// Create TanStack Table column definitions from OutputColumnMetadata
905920
// Calculate column widths based on content
@@ -973,6 +988,8 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
973988
top: 0,
974989
zIndex: 1,
975990
}}
991+
onMouseEnter={() => setIsHeaderRowHovered(true)}
992+
onMouseLeave={() => setIsHeaderRowHovered(false)}
976993
>
977994
{table.getHeaderGroups().map((headerGroup) => (
978995
<tr key={headerGroup.id} style={{ display: "flex", width: "100%" }}>
@@ -1003,6 +1020,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
10031020
sortDirection={header.column.getIsSorted()}
10041021
onSortClick={header.column.getToggleSortingHandler()}
10051022
canSort={header.column.getCanSort()}
1023+
isHeaderRowHovered={isHeaderRowHovered}
10061024
>
10071025
{flexRender(header.column.columnDef.header, header.getContext())}
10081026
</HeaderCellContent>
@@ -1067,6 +1085,8 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
10671085
top: 0,
10681086
zIndex: 1,
10691087
}}
1088+
onMouseEnter={() => setIsHeaderRowHovered(true)}
1089+
onMouseLeave={() => setIsHeaderRowHovered(false)}
10701090
>
10711091
{/* Main header row */}
10721092
{table.getHeaderGroups().map((headerGroup) => (
@@ -1098,6 +1118,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
10981118
sortDirection={header.column.getIsSorted()}
10991119
onSortClick={header.column.getToggleSortingHandler()}
11001120
canSort={header.column.getCanSort()}
1121+
isHeaderRowHovered={isHeaderRowHovered}
11011122
>
11021123
{flexRender(header.column.columnDef.header, header.getContext())}
11031124
</HeaderCellContent>

0 commit comments

Comments
 (0)