Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"stats": "vite build && open stats.html"
},
"dependencies": {
"@quent/components": "workspace:*",
"@quent/client": "workspace:*",
"@quent/components": "workspace:*",
"@quent/hooks": "workspace:*",
"@quent/utils": "workspace:*",
"@radix-ui/react-accordion": "^1.2.12",
Expand All @@ -44,6 +44,7 @@
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-router": "^1.169.9",
Expand Down Expand Up @@ -95,4 +96,4 @@
"vite": "^7.3.2",
"vitest": "^4.0.18"
}
}
}
37 changes: 20 additions & 17 deletions ui/packages/@quent/components/src/dag/DAGControls.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible';
import { SelectField, type SelectFieldOption } from '../ui/select-field';
import {
useSelectedColorField,
useSelectedEdgeWidthField,
useSelectedEdgeColorField,
useSelectedNodeLabelField,
useNodeColorPalette,
useEdgeColorPalette,
} from '@quent/hooks';
import { NODE_LABEL_FIELD, type NodeLabelField } from '@quent/utils';
import { Palette, Spline, Brush, ChevronDown, Type } from 'lucide-react';
import { DAGSettingsPopover } from './DAGSettingsPopover';
import { useState } from 'react';
import { Palette, Spline, Brush, Type } from 'lucide-react';
import { PalettePicker } from './PalettePicker';

interface DAGControlsProps {
operatorStatFields: string[];
Expand All @@ -33,23 +33,20 @@ export const DAGControls = ({ operatorStatFields, portStatFields, isDark }: DAGC
const [edgeWidthField, setEdgeWidthField] = useSelectedEdgeWidthField();
const [edgeColorField, setEdgeColorField] = useSelectedEdgeColorField();
const [nodeLabelField, setNodeLabelField] = useSelectedNodeLabelField();
const [open, setOpen] = useState(true);
const [nodePalette, setNodePalette] = useNodeColorPalette();
const [edgePalette, setEdgePalette] = useEdgeColorPalette();

const operatorOptions: SelectFieldOption[] = operatorStatFields.map(f => ({ value: f }));
const portOptions: SelectFieldOption[] = portStatFields.map(f => ({ value: f }));

return (
<Collapsible open={open} onOpenChange={setOpen} className="border-b bg-card">
<div className="flex items-center justify-between px-4 py-2">
<CollapsibleTrigger className="flex items-center gap-2 group cursor-pointer">
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
Plan Controls
</span>
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-180" />
</CollapsibleTrigger>
<DAGSettingsPopover isDark={isDark} />
<div className="bg-card">
<div className="px-4 py-2">
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
Plan Controls
</span>
</div>
<CollapsibleContent className="px-4 pb-2 grid grid-cols-1 lg:grid-cols-2 gap-x-3 gap-y-1.5">
<div className="px-4 pb-2 grid grid-cols-1 lg:grid-cols-2 gap-x-3 gap-y-1.5">
<SelectField
label="Node color"
icon={Palette}
Expand All @@ -58,6 +55,9 @@ export const DAGControls = ({ operatorStatFields, portStatFields, isDark }: DAGC
onValueChange={setColorField}
placeholder="None"
triggerClassName="h-6 text-xs"
trailingAdornment={
<PalettePicker value={nodePalette} onValueChange={setNodePalette} isDark={isDark} />
}
/>
<SelectField
label="Edge width"
Expand All @@ -76,6 +76,9 @@ export const DAGControls = ({ operatorStatFields, portStatFields, isDark }: DAGC
onValueChange={setEdgeColorField}
placeholder="None"
triggerClassName="h-6 text-xs"
trailingAdornment={
<PalettePicker value={edgePalette} onValueChange={setEdgePalette} isDark={isDark} />
}
/>
<SelectField
label="Node label"
Expand All @@ -87,7 +90,7 @@ export const DAGControls = ({ operatorStatFields, portStatFields, isDark }: DAGC
clearable={false}
triggerClassName="h-6 text-xs"
/>
</CollapsibleContent>
</Collapsible>
</div>
</div>
);
};
94 changes: 0 additions & 94 deletions ui/packages/@quent/components/src/dag/DAGSettingsPopover.tsx

This file was deleted.

41 changes: 41 additions & 0 deletions ui/packages/@quent/components/src/dag/PalettePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Select, SelectContent, SelectItem, SelectTrigger } from '../ui/select';
import { CONTINUOUS_PALETTES, continuousColor, type ContinuousPaletteName } from '@quent/utils';

const paletteEntries = Object.entries(CONTINUOUS_PALETTES) as [
ContinuousPaletteName,
{ label: string },
][];

interface PalettePickerProps {
value: ContinuousPaletteName;
onValueChange: (value: ContinuousPaletteName) => void;
isDark: boolean;
}

/** Compact color-swatch button that opens a palette selector dropdown. */
export const PalettePicker = ({ value, onValueChange, isDark }: PalettePickerProps) => (
<Select value={value} onValueChange={v => onValueChange(v as ContinuousPaletteName)}>
<SelectTrigger className="h-6 w-6 shrink-0 p-0 rounded-sm border border-border overflow-hidden [&>svg]:hidden focus:ring-1 focus:ring-ring">
<span
className="block w-full h-full"
style={{ background: continuousColor(1, value, isDark) }}
/>
</SelectTrigger>
<SelectContent>
{paletteEntries.map(([key, { label }]) => (
<SelectItem key={key} value={key} className="text-xs">
<div className="flex items-center gap-2">
<span
className="inline-block h-3 w-3 rounded-sm shrink-0"
style={{ background: continuousColor(1, key, isDark) }}
/>
{label}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
);
2 changes: 1 addition & 1 deletion ui/packages/@quent/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export {
TableCell,
TableCaption,
} from './ui/table';
export { Tabs, TabsList, TabsTrigger, TabsContent } from './ui/tabs';

// ─── ECharts ──────────────────────────────────────────────────────────────────
export { echarts } from './lib/echarts';
Expand Down Expand Up @@ -167,7 +168,6 @@ export { DAGChart } from './dag/DAGChart';
export { DAGControls } from './dag/DAGControls';
export { DAGLegend } from './dag/DAGLegend';
export { DAGNodeInfoPanel } from './dag/DAGNodeInfoPanel';
export { DAGSettingsPopover } from './dag/DAGSettingsPopover';

// ─── Query-plan components ────────────────────────────────────────────────────
export { QueryPlanNode } from './query-plan/QueryPlanNode';
Expand Down
9 changes: 8 additions & 1 deletion ui/packages/@quent/components/src/ui/select-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export interface SelectFieldProps {
className?: string;
/** className forwarded to SelectTrigger */
triggerClassName?: string;
/** Custom render function for each option item (replaces default label rendering). */
renderOption?: (option: SelectFieldOption) => React.ReactNode;
/** Node rendered after the select trigger (e.g. a palette swatch button). */
trailingAdornment?: React.ReactNode;
}

/** Select dropdown with optional label, icon, and clear button. */
Expand All @@ -46,6 +50,8 @@ export const SelectField = ({
clearable = true,
className,
triggerClassName,
renderOption,
trailingAdornment,
}: SelectFieldProps) => (
<div className={cn('flex items-center gap-1.5 min-w-0', className)}>
{Icon && <Icon className="h-3 w-3 shrink-0 text-muted-foreground" />}
Expand Down Expand Up @@ -83,13 +89,14 @@ export const SelectField = ({
) : (
options.map(opt => (
<SelectItem key={opt.value} value={opt.value} className="text-xs">
{opt.label ?? opt.value}
{renderOption ? renderOption(opt) : (opt.label ?? opt.value)}
</SelectItem>
))
)}
</SelectGroup>
</ScrollArea>
</SelectContent>
</Select>
{trailingAdornment}
</div>
);
66 changes: 66 additions & 0 deletions ui/packages/@quent/components/src/ui/tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"

import { cn } from '@quent/utils';

const Tabs = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Root
ref={ref}
className={cn("flex flex-col flex-1 overflow-hidden", className)}
{...props}
/>
))
Tabs.displayName = TabsPrimitive.Root.displayName

const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"h-9 w-full shrink-0 inline-flex items-center justify-start rounded-none border-b bg-transparent p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName

const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-normal ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-muted data-[state=active]:font-semibold data-[state=active]:shadow cursor-pointer",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName

export { Tabs, TabsList, TabsTrigger, TabsContent }
4 changes: 2 additions & 2 deletions ui/packages/@quent/components/src/ui/tree-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ function TreeLeaf<T extends TreeDataItem = TreeDataItem>({
return (
<div
className={cn(
'ml-5 flex text-left items-center py-2 cursor-pointer before:right-1',
'ml-5 flex text-left items-center py-1 cursor-pointer before:right-1',
treeVariants(),
className,
isSelected && selectedTreeVariants(),
Expand Down Expand Up @@ -474,7 +474,7 @@ const AccordionTrigger = React.forwardRef<
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 w-full items-center py-2 transition-all first:[&[data-state=open]>svg]:first-of-type:rotate-90 cursor-pointer',
'flex flex-1 w-full items-center py-0.5 transition-all first:[&[data-state=open]>svg]:first-of-type:rotate-90 cursor-pointer',
className
)}
{...props}
Expand Down
Loading
Loading