Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
039c9e3
chore(studio): add @dnd-kit/core and @dnd-kit/sortable
walston Jul 7, 2026
87bc119
chore(studio): add @dnd-kit/utilities and @dnd-kit/modifiers
walston Jul 7, 2026
e016055
feat(studio): add drag handle support to TableColumnHeader
walston Jul 7, 2026
98f4765
feat(studio): add enableColumnReordering to TableContent
walston Jul 7, 2026
2e2df70
feat(studio): enable column drag reordering on experiments table [ASE…
walston Jul 7, 2026
0f78367
fix(studio): suppress lint for dynamic DnD transform inline styles
walston Jul 7, 2026
edbfcfd
feat(studio): persist experiment column order to localStorage per gro…
walston Jul 7, 2026
bac3688
refactor(studio): use useLocalStorage hook for column order persisten…
walston Jul 7, 2026
5034b12
fix(studio): fix import order in TableColumnHeader
walston Jul 7, 2026
8b8ea20
fix(studio): format TableContent.tsx
walston Jul 7, 2026
3ec54d1
fix(studio): restore cell sizing, pinning, a11y, and tooltips in Drag…
walston Jul 7, 2026
112f040
fix(studio): decouple drag activator from sort hit area using setActi…
walston Jul 7, 2026
797ab41
Revert "fix(studio): decouple drag activator from sort hit area using…
walston Jul 7, 2026
3337ff0
feat(studio): use ButtonGroup for grip + sort in column headers [ASE-…
walston Jul 8, 2026
8e66b2b
fix(studio): replace ButtonGroup with flex wrapper for grip + sort la…
walston Jul 8, 2026
ded2b6c
fix(studio): use DraggableSyntheticListeners type and move drag attri…
walston Jul 8, 2026
332b93a
fix(studio): add sortableKeyboardCoordinates to KeyboardSensor for co…
walston Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion web/packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
"axios": "catalog:",
"classnames": "catalog:",
"handlebars": "catalog:",
"hyparquet": "catalog:",
"lucide-react": "catalog:",
"openai": "catalog:",
"openapi-fetch": "^0.13.3",
"p-limit": "catalog:",
"hyparquet": "catalog:",
"papaparse": "^5.5.3",
"react": "catalog:",
"react-dom": "catalog:",
Expand Down Expand Up @@ -76,6 +76,10 @@
"@codemirror/lint": "^6.9.2",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.39.4",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-table": "8.20.5",
"@tanstack/react-virtual": "3.13.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { DraggableAttributes, DraggableSyntheticListeners } from '@dnd-kit/core';
import { useInnerDataViewContext } from '@nemo/common/src/components/DataView/internal/context';
import { useHandleResize } from '@nemo/common/src/components/DataView/internal/hooks/useResizableColumns';
import type { IntentionalAny } from '@nemo/common/src/components/DataView/internal/types';
Expand All @@ -9,17 +10,27 @@ import { Button, TableHeaderCell } from '@nvidia/foundations-react-core';
import { childrenToText } from '@nvidia/foundations-react-core/lib';
import { flexRender, type Header, type SortDirection } from '@tanstack/react-table';
import classnames from 'classnames';
import { ArrowUp, ArrowUpDown } from 'lucide-react';
import type { ComponentProps, JSX, ReactNode } from 'react';
import { ArrowUp, ArrowUpDown, GripVertical } from 'lucide-react';
import type { ComponentProps, JSX, ReactNode, Ref } from 'react';

interface DragProps {
setNodeRef?: (node: HTMLElement | null) => void;
setActivatorNodeRef?: (node: HTMLElement | null) => void;
attributes: DraggableAttributes;
listeners: DraggableSyntheticListeners;
isDragging: boolean;
}

interface TableColumnHeaderProps extends ComponentProps<typeof TableHeaderCell> {
automaticTitles?: boolean;
header: Header<IntentionalAny, unknown>;
dragProps?: DragProps;
}

export function TableColumnHeader({
automaticTitles,
className,
dragProps,
header,
...props
}: TableColumnHeaderProps): JSX.Element {
Expand All @@ -30,13 +41,15 @@ export function TableColumnHeader({
const headerMeta = header.column.columnDef.meta;
return (
<TableHeaderCell
ref={dragProps?.setNodeRef as Ref<HTMLTableCellElement> | undefined}
align={headerMeta?.headerAlignment ?? headerMeta?.alignment}
className={classnames(
className,
'group',
'data-[pinned]:sticky data-[pinned]:z-10',
'[&>.data-view-header-control]:-mx-[var(--table-cell-inline-padding)] [&>.data-view-header-control]:-my-[var(--table-cell-block-padding)]',
'[&>.data-view-header-control]:max-w-[calc(100%+var(--table-cell-inline-padding)*2-4px)]'
'[&>.data-view-header-control]:max-w-[calc(100%+var(--table-cell-inline-padding)*2-4px)]',
dragProps?.isDragging && 'opacity-50'
)}
colSpan={header.column.columns.length > 1 ? header.column.columns.length : undefined}
data-pinned={header.column.getIsPinned() || undefined}
Expand All @@ -46,6 +59,7 @@ export function TableColumnHeader({
>
<TableHeaderControlCell
disabled={isDataViewLoadingState || isDataViewErrorState}
dragProps={dragProps}
header={header}
>
{children as ReactNode}
Expand All @@ -63,25 +77,65 @@ export function TableColumnHeader({
interface TableHeaderControlCellProps {
children: ReactNode;
disabled: boolean;
dragProps?: DragProps;
header: Header<IntentionalAny, unknown>;
}

function TableHeaderControlCell({
children,
disabled,
dragProps,
header,
...props
}: TableHeaderControlCellProps): JSX.Element {
if (!header.column.getCanSort() || disabled) {
return <>{children}</>;
}
if (disabled) return <>{children}</>;

const canSort = header.column.getCanSort();
const sort = header.column.getIsSorted();
const grip = dragProps?.listeners ? (
<button
ref={dragProps.setActivatorNodeRef}
className="cursor-grab active:cursor-grabbing p-0.5 text-secondary hover:text-primary focus:outline-none shrink-0"
aria-label="Drag to reorder column"
type="button"
{...dragProps.attributes}
{...dragProps.listeners}
>
<GripVertical size={14} />
</button>
) : null;

// Neither interactive — plain label
if (!canSort && !grip) return <>{children}</>;

// Drag only — grip alongside plain label, no sort button
if (!canSort) {
return (
<>
{grip}
{children}
</>
);
}

// Sort + drag — flex wrapper keeps them together without ButtonGroup chrome
if (grip) {
return (
<div className="data-view-header-control flex items-center">
{grip}
<Button kind="tertiary" onClick={header.column.getToggleSortingHandler()}>
<span className="truncate leading-[normal]">{children}</span>
<SortIcon sort={sort} />
</Button>
</div>
);
}

// Sort only — current behavior
return (
<Button
className="data-view-header-control"
kind="tertiary"
onClick={header.column.getToggleSortingHandler()}
{...props}
>
<span className="truncate leading-[normal]">{children}</span>
<SortIcon sort={sort} />
Expand Down
Loading