From fa50d21811d6799df067ed030952122eb6e5752c Mon Sep 17 00:00:00 2001 From: idastambuk Date: Thu, 8 Jan 2026 14:59:15 +0100 Subject: [PATCH] Add drag & drop for new button --- .../src/components/Sidebar/Sidebar.tsx | 2 +- .../edit-pane/DashboardEditPaneRenderer.tsx | 10 ++- .../edit-pane/DashboardSidePaneNew.tsx | 80 +++++++++++++------ .../scene/DashboardLayoutOrchestrator.tsx | 43 +++++++++- 4 files changed, 105 insertions(+), 30 deletions(-) diff --git a/packages/grafana-ui/src/components/Sidebar/Sidebar.tsx b/packages/grafana-ui/src/components/Sidebar/Sidebar.tsx index 311e11f390c..efe528b186a 100644 --- a/packages/grafana-ui/src/components/Sidebar/Sidebar.tsx +++ b/packages/grafana-ui/src/components/Sidebar/Sidebar.tsx @@ -46,7 +46,7 @@ export function SidebarComp({ children, contextValue }: Props) { return ( -
+
{!tabsMode && } {children}
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx index 34909cb49f9..008fcc2824a 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx @@ -36,7 +36,11 @@ export function DashboardEditPaneRenderer({ editPane, dashboard }: Props) { const hasUid = Boolean(uid); const selectedObject = selection?.getFirstObject(); const isNewElement = selection?.isNewElement() ?? false; - const [selectedLayoutElement, setSelectedLayoutElement] = useState(selectedObject); + // the layout element that was selected when opening the 'add' pane + // used when adding new panel from the sidebar + const [selectedLayoutElement, setSelectedLayoutElement] = useState>( + dashboard + ); const editableElement = useMemo(() => { if (selection) { @@ -46,7 +50,7 @@ export function DashboardEditPaneRenderer({ editPane, dashboard }: Props) { return undefined; }, [selection]); - // saves the row or tab selected when opening the 'add' pane, so new panels can later be added to it + const onSetLayoutElement = (obj: SceneObject | undefined) => { if (obj instanceof RowItem || obj instanceof TabItem) { setSelectedLayoutElement(obj); @@ -80,7 +84,7 @@ export function DashboardEditPaneRenderer({ editPane, dashboard }: Props) { )} {openPane === 'add' && ( - + )} {openPane === 'outline' && ( diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardSidePaneNew.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardSidePaneNew.tsx index 7e78ae6f584..6867aadd1c2 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardSidePaneNew.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardSidePaneNew.tsx @@ -1,30 +1,60 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; +import { SceneObject } from '@grafana/scenes'; import { Sidebar, Text, useStyles2 } from '@grafana/ui'; import addPanelImg from 'img/dashboards/add-panel.png'; +import { getDashboardSceneFor } from '../utils/utils'; -export function DashboardSidePaneNew({ onAddPanel }: {onAddPanel: () => void}) { +export function DashboardSidePaneNew({ onAddPanel, dashboard }: { onAddPanel: () => void; dashboard: SceneObject }) { const styles = useStyles2(getStyles); + const orchestrator = getDashboardSceneFor(dashboard).state.layoutOrchestrator; return ( -
- -
- {t('dashboard.add.new-panel.title', 'Panel')} - - {t('dashboard.add.new-panel.description', 'Drag or click to add a panel')} - - -
- -
-
-
+ orchestrator?.startDraggingNewPanel()} onDragEnd={() => {}}> + + {(dropProvided) => ( +
+ +
+ {t('dashboard.add.new-panel.title', 'Panel')} + + {t('dashboard.add.new-panel.description', 'Drag or click to add a panel')} + +
+ + {(dragProvided, dragSnapshot) => { + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onAddPanel(); + } + }} + aria-label={t('dashboard.add.new-panel.title', 'Panel')} + > + Add panel click area +
+ ); + }} +
+
+
+
+ )} +
+
); } @@ -33,24 +63,28 @@ function getStyles(theme: GrafanaTheme2) { sidePanel: css({ borderBottom: `1px solid ${theme.colors.border.weak}`, }), + dragging: css({ + cursor: 'move', + }), sidePanelContainer: css({ padding: theme.spacing(2), span: { display: 'block', }, }), + dragContainer: css({ + height: 150, + }), imageContainer: css({ - opacity: 0.8, + cursor: 'pointer', + opacity: 0.8, + overflow: 'hidden', padding: theme.spacing(2, 0), borderRadius: theme.shape.radius.sm, - cursor: 'pointer', width: '100%', '&:hover': { opacity: 1, - } - }), - noStyles: css({ - all: 'unset', + }, }), }; } diff --git a/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx b/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx index 94484509991..55c4cfb9983 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardLayoutOrchestrator.tsx @@ -11,11 +11,16 @@ import { } from '@grafana/scenes'; import { createPointerDistance } from '@grafana/ui'; +import { getDefaultVizPanel } from '../utils/utils'; + import { DashboardScene } from './DashboardScene'; +import { RowItem } from './layout-rows/RowItem'; +import { TabItem } from './layout-tabs/TabItem'; import { DashboardDropTarget, isDashboardDropTarget } from './types/DashboardDropTarget'; interface DashboardLayoutOrchestratorState extends SceneObjectState { draggingGridItem?: SceneObjectRef; + isDraggingNewPanel?: boolean; } export class DashboardLayoutOrchestrator extends SceneObjectBase { @@ -59,10 +64,19 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase { @@ -76,12 +90,35 @@ export class DashboardLayoutOrchestrator extends SceneObjectBase el.closest('[data-testid="dashboard-edit-pane-sidebar"]') !== null); + + if (isInSidebar) { + return; + } + const panel = getDefaultVizPanel(); + if (!this._lastDropTarget) { + // if no lastDropTarget and not in Sidebar, treat the dashboard itself as the drop target + this._getDashboard().addPanel(panel); } + if (this._lastDropTarget instanceof RowItem || this._lastDropTarget instanceof TabItem) { + this._lastDropTarget.getLayout().addPanel(panel); + } document.body.removeEventListener('pointermove', this._onPointerMove); - document.body.removeEventListener('pointerup', this._stopDraggingSync); + document.body.removeEventListener('pointerup', this._stopDraggingSync, true); - this.setState({ draggingGridItem: undefined }); + this.setState({ isDraggingNewPanel: false }); } private _onPointerMove(evt: PointerEvent) {