From e7db0c8e695a4dc8b3d2d746b477895217781c06 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 23 Jul 2026 19:18:22 +0100 Subject: [PATCH 01/19] feat(dashboards): move tile editor to a drawer with docked settings panel Convert the dashboard tile create/edit modal into a right-side drawer. Display Settings (and the heatmap variant) now dock as a full-height side panel beside the editor/preview instead of stacking a second drawer, so the tile stays visible while its options change and a single Esc closes only the panel. The display-type tabs sit in a full-width bar above the editor+panel row so they never wrap, and switching to a tab without settings (Search / Patterns / Markdown) or the other variant closes the non-applicable panel. Co-authored-by: Cursor --- .changeset/tile-editor-drawer.md | 5 + packages/app/src/DBDashboardPage.tsx | 26 +- .../components/ChartDisplaySettingsDrawer.tsx | 354 ++++++++-------- .../DBEditTimeChartForm/EditTimeChartForm.tsx | 382 +++++++++++------- .../src/components/HeatmapSettingsDrawer.tsx | 162 +++++--- .../app/src/components/SettingsSidePanel.tsx | 73 ++++ 6 files changed, 616 insertions(+), 386 deletions(-) create mode 100644 .changeset/tile-editor-drawer.md create mode 100644 packages/app/src/components/SettingsSidePanel.tsx diff --git a/.changeset/tile-editor-drawer.md b/.changeset/tile-editor-drawer.md new file mode 100644 index 0000000000..d7f9e2d646 --- /dev/null +++ b/.changeset/tile-editor-drawer.md @@ -0,0 +1,5 @@ +--- +'@hyperdx/app': patch +--- + +feat: Move dashboard tile creation/editing from a modal into a right-side drawer. Display Settings (including the heatmap variant) now dock as a full-height side panel next to the editor/preview instead of stacking a second drawer, so the tile stays visible while its display options are changed and a single Esc press just closes the panel rather than closing everything at once. diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 6ef9c691bb..575d06d80a 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -63,11 +63,11 @@ import { Box, Breadcrumbs, Button, + Drawer, Flex, Group, Indicator, Menu, - Modal, Paper, Popover, Portal, @@ -1391,7 +1391,7 @@ const Tile = forwardRef( }, ); -const EditTileModal = ({ +const EditTileDrawer = ({ dashboardId, chart, onClose, @@ -1407,7 +1407,7 @@ const EditTileModal = ({ onSave: (chart: Tile) => void; }) => { const contextZIndex = useZIndex(); - const modalZIndex = contextZIndex + 10; + const drawerZIndex = contextZIndex + 10; const confirm = useConfirm(); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); @@ -1442,18 +1442,22 @@ const EditTileModal = ({ }, [confirm, isSaving, hasUnsavedChanges, onClose]); return ( - {chart != null && ( - - {/* Isolate chart cross-syncing to this edit modal: the preview chart + + {/* Isolate chart cross-syncing to this edit drawer: the preview chart must not drive shadow tooltips on the dashboard tiles behind it. */} )} - + ); }; @@ -2860,7 +2864,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { - { diff --git a/packages/app/src/components/ChartDisplaySettingsDrawer.tsx b/packages/app/src/components/ChartDisplaySettingsDrawer.tsx index ba38dac8d1..a4a87d6c66 100644 --- a/packages/app/src/components/ChartDisplaySettingsDrawer.tsx +++ b/packages/app/src/components/ChartDisplaySettingsDrawer.tsx @@ -32,6 +32,7 @@ import { import { ColorSwatchInput } from './ColorSwatchInput'; import { CheckBoxControlled } from './InputControlled'; import { DEFAULT_NUMBER_FORMAT, NumberFormatForm } from './NumberFormat'; +import SettingsSidePanel from './SettingsSidePanel'; export type ChartConfigDisplaySettings = Pick< ChartConfigWithDateRange, @@ -76,6 +77,14 @@ interface ChartDisplaySettingsDrawerProps { onChange: (settings: ChartConfigDisplaySettings, isDirty: boolean) => void; onClose: () => void; isPerSeriesNumberFormatAllowed?: boolean; + /** + * Render the settings as an in-place side panel (no Drawer overlay) instead + * of a nested drawer. Used inside the tile editor drawer so opening Display + * Settings docks a panel beside the editor/preview rather than stacking a + * second drawer on top (which made a single Esc press close both) — and keeps + * the tile visible while its display options are changed. + */ + asPanel?: boolean; } function applyDefaultSettings( @@ -115,6 +124,7 @@ export default function ChartDisplaySettingsDrawer({ onClose, previousDateRange, isPerSeriesNumberFormatAllowed = false, + asPanel = false, }: ChartDisplaySettingsDrawerProps) { const appliedDefaults = useMemo( () => applyDefaultSettings(settings, defaultNumberFormat), @@ -213,83 +223,50 @@ export default function ChartDisplaySettingsDrawer({ const showBackgroundChart = displayType === DisplayType.Number; const isBackgroundChartDisabled = configType === 'sql'; - return ( - - - {isTimeChart && ( - <> - + {isTimeChart && ( + <> + + + { + setValue('fillNulls', e.currentTarget.checked ? 0 : false); + }} /> - - { - setValue('fillNulls', e.currentTarget.checked ? 0 : false); - }} - /> - - - ( - - {' - '} - ) - - ) - } - /> - - {showSeriesLimit && ( - - ( - - onChange(v === '' || v == null ? null : Number(v)) - } - /> - )} - /> - - )} - - - )} - - {showCategoricalLimit && ( - <> + + + ( + + {' - '} + ) + + ) + } + /> + + {showSeriesLimit && ( - - - )} + )} + + + )} - {showTableOptions && ( - <> - {showGroupByColumnsOnLeft && ( - - )} + {showCategoricalLimit && ( + <> + + ( + + onChange(v === '' || v == null ? null : Number(v)) + } + /> + )} + /> + + + + )} + + {showTableOptions && ( + <> + {showGroupByColumnsOnLeft && ( - - - )} - - {showTileColor && ( - <> - - - Color - - ( - - )} - /> - - - ( - - )} - /> - - - - )} + )} + + + + )} - {showBackgroundChart && ( - <> + {showTileColor && ( + <> + + + Color + ( - )} /> - - - )} + + + ( + + )} + /> + + + + )} + + {showBackgroundChart && ( + <> + ( + + )} + /> + + + )} - - - Format may be overridden on individual series. - - - ) : undefined - } - /> - - - - - - + + + Format may be overridden on individual series. + + + ) : undefined + } + /> + + + + + + + ); + + // Panel mode: dock the settings as a full-height side panel beside the editor + // instead of stacking a second drawer, so the tile/preview stays visible + // while its display options are changed. + if (asPanel) { + if (!opened) return null; + return ( + + {content} + + ); + } + + return ( + + {content} ); } diff --git a/packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx b/packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx index 625a61025a..8e10373ca5 100644 --- a/packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx +++ b/packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx @@ -346,6 +346,54 @@ export default function EditTimeChartForm({ { open: openHeatmapSettings, close: closeHeatmapSettings }, ] = useDisclosure(false); + // On a dashboard the editor lives inside a Drawer, so opening a settings + // view (regular Display Settings or the heatmap variant) docks a side panel + // next to the editor/preview instead of stacking a second drawer. On the + // Chart Explorer page the editor is not in a drawer, so they keep their own + // overlay drawer. + const showSettingsPanel = + isDashboardForm && (displaySettingsOpened || heatmapSettingsOpened); + + // While a settings side panel is open, Esc should just close the panel rather + // than closing the whole tile drawer. Intercept on the capture phase and stop + // propagation so the drawer's own (bubble-phase) Esc handler never fires — + // this is what fixes a single Esc press closing both. + useEffect(() => { + if (!showSettingsPanel) return; + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.stopPropagation(); + closeDisplaySettings(); + closeHeatmapSettings(); + } + }; + window.addEventListener('keydown', handleKeyDown, true); + return () => window.removeEventListener('keydown', handleKeyDown, true); + }, [showSettingsPanel, closeDisplaySettings, closeHeatmapSettings]); + + // Whether the current tab has a regular Display Settings view. Search, + // Patterns and Markdown have none; Heatmap uses its own variant instead. + const displayTypeSupportsDisplaySettings = + displayType !== DisplayType.Search && + displayType !== DisplayType.EventPatterns && + displayType !== DisplayType.Markdown && + displayType !== DisplayType.Heatmap; + + // On tab change, close any settings panel that doesn't belong to the new tab + // so a stale panel (e.g. Number's Display Settings) is never shown on a tab + // without one, or the wrong variant (heatmap vs. regular) lingers. + useEffect(() => { + if (displayType === previousDisplayType) return; + if (!displayTypeSupportsDisplaySettings) closeDisplaySettings(); + if (displayType !== DisplayType.Heatmap) closeHeatmapSettings(); + }, [ + displayType, + previousDisplayType, + displayTypeSupportsDisplaySettings, + closeDisplaySettings, + closeHeatmapSettings, + ]); + // Only update this on submit, otherwise we'll have issues // with using the source value from the last submit // (ex. ignoring local custom source updates) @@ -709,8 +757,22 @@ export default function EditTimeChartForm({ ); return ( -
- +
+ {/* Full-width display-type tab bar. Kept above the editor + Display + Settings row so the tabs never wrap when the panel is open. */} + @@ -781,166 +842,195 @@ export default function EditTimeChartForm({ )} /> - - - Chart Name - - - {isRawSqlDisplayType(displayType) && ( - ( - + {/* Editor + Display Settings side by side, below the tab bar, so the + tile/preview stays visible while its display options are changed. + In the dashboard drawer the row fills the remaining height: the + editor column scrolls on its own and the settings dock full-height + beside it. On Chart Explorer the settings return null and fall back + to the overlay Drawer. */} + + + + + + Chart Name + + + {isRawSqlDisplayType(displayType) && ( + ( + + )} /> )} - /> - )} - - - {activeTab === 'markdown' ? ( -
-