use useId where we can

This commit is contained in:
joshhunt
2025-08-26 17:44:00 +01:00
parent aed40e2d15
commit 34090ac75d
4 changed files with 90 additions and 80 deletions
@@ -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: () => (
<Stack direction="row" gap={1} alignItems="center">
<div>{title}</div>
@@ -54,7 +58,7 @@ export function useConditionalRenderingEditor(
}).addItem(
new OptionsPaneItemDescriptor({
title,
id: 'conditional-rendering-options-item',
id: itemId,
render: () => <conditionalRenderingToRender.Component model={conditionalRenderingToRender} />,
})
);
@@ -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<Props>(({ 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();
@@ -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 <PanelFrameTitleInput id={descriptor.props.id} panel={panel} />;
},
addon: config.featureToggles.dashgpt && (
<GenAIPanelTitleButton
onGenerate={(title) => 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 <PanelDescriptionTextArea id={descriptor.props.id} panel={panel} />;
},
addon: config.featureToggles.dashgpt && (
<GenAIPanelDescriptionButton
onGenerate={(description) => 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 <PanelBackgroundSwitch id={descriptor.props.id} panel={panel} />;
},
})
)
.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: () => <ScenePanelLinksEditor panelLinks={panelLinksObject ?? undefined} />,
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 <PanelFrameTitleInput id={descriptor.props.id} panel={panel} />;
},
addon: config.featureToggles.dashgpt && (
<GenAIPanelTitleButton
onGenerate={(title) => 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 <PanelDescriptionTextArea id={descriptor.props.id} panel={panel} />;
},
addon: config.featureToggles.dashgpt && (
<GenAIPanelDescriptionButton
onGenerate={(description) => 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 <PanelBackgroundSwitch id={descriptor.props.id} panel={panel} />;
},
})
)
.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: () => <ScenePanelLinksEditor panelLinks={panelLinksObject ?? undefined} />,
})
)
);
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 {
@@ -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) => <TabTitleInput id={descriptor.props.id} tab={model} isNewElement={isNewElement} />,
})
),
[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) => <TabRepeatSelect id={descriptor.props.id} tab={model} />,
})
),
[model]
[model, repeatId]
);
const layoutCategory = useLayoutCategory(layout);