From 34090ac75d31cbbea1b945edc26490c700108d86 Mon Sep 17 00:00:00 2001 From: joshhunt Date: Tue, 26 Aug 2025 17:44:00 +0100 Subject: [PATCH] use useId where we can --- .../ConditionalRenderingEditor.tsx | 8 +- .../panel-edit/PanelOptions.tsx | 4 +- .../panel-edit/getPanelFrameOptions.tsx | 144 +++++++++--------- .../scene/layout-tabs/TabItemEditor.tsx | 14 +- 4 files changed, 90 insertions(+), 80 deletions(-) diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx index 30b958a1642..c36c359df44 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx @@ -1,3 +1,5 @@ +import { useId } from 'react'; + import { t } from '@grafana/i18n'; import { Icon, Stack, Tooltip } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; @@ -25,6 +27,8 @@ export function useConditionalRenderingEditor( disabledText?: string ): OptionsPaneCategoryDescriptor { const title = t('dashboard.conditional-rendering.root.title', 'Show / hide rules'); + const categoryId = useId(); + const itemId = useId(); const conditionalRenderingToRender = conditionalRendering ?? getPlaceholderConditionalRendering(); @@ -37,7 +41,7 @@ export function useConditionalRenderingEditor( 'dashboard.conditional-rendering.editor.unsupported-item-type', 'Conditional rendering not supported for this item type' )), - id: 'conditional-rendering-options', + id: categoryId, renderTitle: () => (
{title}
@@ -54,7 +58,7 @@ export function useConditionalRenderingEditor( }).addItem( new OptionsPaneItemDescriptor({ title, - id: 'conditional-rendering-options-item', + id: itemId, render: () => , }) ); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx index fd373104caf..c882f10c364 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx @@ -13,7 +13,7 @@ import { import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; import { getLibraryPanelBehavior, isLibraryPanel } from '../utils/utils'; -import { getPanelFrameOptions } from './getPanelFrameOptions'; +import { usePanelFrameOptions } from './getPanelFrameOptions'; interface Props { panel: VizPanel; @@ -25,7 +25,7 @@ interface Props { export const PanelOptions = React.memo(({ panel, searchQuery, listMode, data }) => { const { options, fieldConfig, _pluginInstanceState } = panel.useState(); - const panelFrameOptions = useMemo(() => getPanelFrameOptions(panel), [panel]); + const panelFrameOptions = usePanelFrameOptions(panel); const visualizationOptions = useMemo(() => { const plugin = panel.getPlugin(); diff --git a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx index c78b7a1a6b1..b84433cfceb 100644 --- a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useId, useMemo } from 'react'; import { CoreApp } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -21,82 +21,86 @@ import { vizPanelToPanel, transformSceneToSaveModel } from '../serialization/tra import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor } from '../utils/utils'; -export function getPanelFrameOptions(panel: VizPanel): OptionsPaneCategoryDescriptor { - const descriptor = new OptionsPaneCategoryDescriptor({ - title: t('dashboard-scene.get-panel-frame-options.descriptor.title.panel-options', 'Panel options'), - id: 'Panel options', - isOpenDefault: true, - }); +export function usePanelFrameOptions(panel: VizPanel): OptionsPaneCategoryDescriptor { + const id = useId(); - const panelLinksObject = dashboardSceneGraph.getPanelLinks(panel); - const links = panelLinksObject?.state.rawLinks ?? []; - const dashboard = getDashboardSceneFor(panel); - const layoutElement = panel.parent!; + return useMemo(() => { + const descriptor = new OptionsPaneCategoryDescriptor({ + title: t('dashboard-scene.get-panel-frame-options.descriptor.title.panel-options', 'Panel options'), + id: id + '-category', + isOpenDefault: true, + }); - descriptor - .addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard-scene.get-panel-frame-options.title.title', 'Title'), - id: 'panel-frame-options-title', - value: panel.state.title, - popularRank: 1, - render: function renderTitle(descriptor) { - return ; - }, - addon: config.featureToggles.dashgpt && ( - editPanelTitleAction(panel, title)} - panel={vizPanelToPanel(panel)} - dashboard={transformSceneToSaveModel(dashboard)} - /> - ), - }) - ) - .addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard-scene.get-panel-frame-options.title.description', 'Description'), - id: 'panel-frame-options-description', - value: panel.state.description, - render: function renderDescription(descriptor) { - return ; - }, - addon: config.featureToggles.dashgpt && ( - panel.setState({ description })} - panel={vizPanelToPanel(panel)} - /> - ), - }) - ) - .addItem( - new OptionsPaneItemDescriptor({ - title: t('dashboard-scene.get-panel-frame-options.title.transparent-background', 'Transparent background'), - id: 'panel-frame-options-transparent-bg', - render: function renderTransparent(descriptor) { - return ; - }, - }) - ) - .addCategory( - new OptionsPaneCategoryDescriptor({ - title: t('dashboard-scene.get-panel-frame-options.title.panel-links', 'Panel links'), - id: 'Panel links', - isOpenDefault: false, - itemsCount: links?.length, - }).addItem( + const panelLinksObject = dashboardSceneGraph.getPanelLinks(panel); + const links = panelLinksObject?.state.rawLinks ?? []; + const dashboard = getDashboardSceneFor(panel); + const layoutElement = panel.parent!; + + descriptor + .addItem( new OptionsPaneItemDescriptor({ - title: t('dashboard-scene.get-panel-frame-options.title.panel-links', 'Panel links'), - id: 'panel-frame-options-panel-links', - render: () => , + title: t('dashboard-scene.get-panel-frame-options.title.title', 'Title'), + id: id + '-title', + value: panel.state.title, + popularRank: 1, + render: function renderTitle(descriptor) { + return ; + }, + addon: config.featureToggles.dashgpt && ( + editPanelTitleAction(panel, title)} + panel={vizPanelToPanel(panel)} + dashboard={transformSceneToSaveModel(dashboard)} + /> + ), }) ) - ); + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard-scene.get-panel-frame-options.title.description', 'Description'), + id: id + '-description', + value: panel.state.description, + render: function renderDescription(descriptor) { + return ; + }, + addon: config.featureToggles.dashgpt && ( + panel.setState({ description })} + panel={vizPanelToPanel(panel)} + /> + ), + }) + ) + .addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard-scene.get-panel-frame-options.title.transparent-background', 'Transparent background'), + id: id + '-bg', + render: function renderTransparent(descriptor) { + return ; + }, + }) + ) + .addCategory( + new OptionsPaneCategoryDescriptor({ + title: t('dashboard-scene.get-panel-frame-options.title.panel-links', 'Panel links'), + id: id + '-panel-links', + isOpenDefault: false, + itemsCount: links?.length, + }).addItem( + new OptionsPaneItemDescriptor({ + title: t('dashboard-scene.get-panel-frame-options.title.panel-links', 'Panel links'), + id: id + '-panel-links-item', + render: () => , + }) + ) + ); - if (isDashboardLayoutItem(layoutElement)) { - layoutElement.getOptions?.().forEach((category) => descriptor.addCategory(category)); - } + if (isDashboardLayoutItem(layoutElement)) { + layoutElement.getOptions?.().forEach((category) => descriptor.addCategory(category)); + } - return descriptor; + return descriptor; + }, [id, panel]); } interface ScenePanelLinksEditorProps { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx index ba88110e4a2..907af4dcecf 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useId, useMemo } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; @@ -18,29 +18,31 @@ import { TabItem } from './TabItem'; export function useEditOptions(model: TabItem, isNewElement: boolean): OptionsPaneCategoryDescriptor[] { const { layout } = model.useState(); + const titleId = useId(); + const repeatId = useId(); const tabCategory = useMemo( () => new OptionsPaneCategoryDescriptor({ title: '', id: 'tab-item-options' }).addItem( new OptionsPaneItemDescriptor({ title: t('dashboard.tabs-layout.tab-options.title-option', 'Title'), - id: 'tab-options-title', + id: titleId, render: (descriptor) => , }) ), - [model, isNewElement] + [model, titleId, isNewElement] ); const repeatCategory = useMemo( () => new OptionsPaneCategoryDescriptor({ title: t('dashboard.tabs-layout.tab-options.repeat.title', 'Repeat options'), - id: 'repeat-options', + id: repeatId + '-category', isOpenDefault: false, }).addItem( new OptionsPaneItemDescriptor({ title: t('dashboard.tabs-layout.tab-options.repeat.variable.title', 'Repeat by variable'), - id: 'tab-options-repeat-variable', + id: repeatId + '-variable', description: t( 'dashboard.tabs-layout.tab-options.repeat.variable.description', 'Repeat this tab for each value in the selected variable.' @@ -48,7 +50,7 @@ export function useEditOptions(model: TabItem, isNewElement: boolean): OptionsPa render: (descriptor) => , }) ), - [model] + [model, repeatId] ); const layoutCategory = useLayoutCategory(layout);