addonbar
Torkel Ödegaard 6 months ago
parent 48573ea669
commit 113201c0b5
  1. 1
      packages/grafana-data/src/types/icon.ts
  2. 19
      public/app/core/components/AppChrome/AddonBar/AddonBar.tsx
  3. 26
      public/app/core/components/AppChrome/AddonBar/AddonBarPane.tsx
  4. 21
      public/app/core/components/AppChrome/AddonBar/DeclareAddonApp.tsx
  5. 84
      public/app/core/components/AppChrome/AddonBar/HelpPane.tsx
  6. 6
      public/app/core/components/AppChrome/AppChrome.tsx
  7. 50
      public/app/core/components/AppChrome/AppChromeService.tsx
  8. 1
      public/app/core/components/AppChrome/History/HistoryContainer.tsx
  9. 2
      public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx
  10. 31
      public/app/features/dashboard-scene/edit-pane/DashboardContentPane.tsx
  11. 3
      public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx
  12. 204
      public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx
  13. 2
      public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
  14. 24
      public/app/features/dashboard-scene/edit-pane/DashboardFilters.tsx
  15. 9
      public/app/features/dashboard-scene/edit-pane/DashboardSettingsPane.tsx
  16. 23
      public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx
  17. 7
      public/app/features/dashboard-scene/edit-pane/SelectedObjectPane.tsx
  18. 2
      public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx
  19. 2
      public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx
  20. 7
      public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx

@ -231,6 +231,7 @@ export const availableIconsIndex = {
stopwatch: true,
'stopwatch-slash': true,
sync: true,
'focus-target': true,
'sync-slash': true,
table: true,
'table-collapse-all': true,

@ -31,10 +31,10 @@ export function AddonBar() {
const unifiedHistoryEnabled = config.featureToggles.unifiedHistory;
const onToggleAddonBar = () => {
chrome.update({ addonBar: !state.addonBar, addonBarPane: undefined });
chrome.update({ addonBarDocked: !state.addonBarDocked, addonBarPane: undefined });
};
if (!state.addonBar) {
if (!state.addonBarDocked) {
return (
<button type="button" className={cx(styles.toggleButton, styles.expandButton)} onClick={onToggleAddonBar}>
<Icon name="angle-left" size="xl" />
@ -69,6 +69,18 @@ export function AddonBar() {
/>
</Dropdown>
)}
<div style={{ marginBottom: '32px' }}></div>
{state.addonApps.map((app) => (
<AddonBarItem active={state.addonBarPane?.id === app.id} key={app.id}>
<ToolbarButton
tooltip={app.title}
iconOnly
icon={app.icon}
aria-label={app.title}
onClick={() => chrome.openAddon(app.id)}
/>
</AddonBarItem>
))}
<FlexItem grow={1} />
<LineSeparator />
<AddonBarItem>
@ -150,7 +162,8 @@ function getStyles(theme: GrafanaTheme2) {
},
}),
profileButton: css({
padding: theme.spacing(0, 0.5),
padding: 0,
justifyContent: 'center',
img: {
borderRadius: theme.shape.radius.circle,
height: '24px',

@ -3,24 +3,28 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { AddonBarPane as AddonBarPaneType } from '../AppChromeService';
import { TOP_BAR_LEVEL_HEIGHT } from '../types';
import { ADDON_BAR_WIDTH } from './AddonBar';
export interface Props {
pane?: AddonBarPaneType;
title: string;
actions?: React.ReactNode;
children: React.ReactNode;
}
export const ADDON_BAR_PANE_WIDTH = 280;
export function AddonBarPane({ pane }: Props) {
export function AddonBarPane({ title, actions, children }: Props) {
const styles = useStyles2(getStyles);
return (
<div className={styles.pane}>
<div className={styles.header}>{pane?.title}</div>
<div className={styles.content}>{pane?.content}</div>
<div className={styles.header}>
<div>{title}</div>
<div className={styles.headerActions}>{actions}</div>
</div>
<div className={styles.content}>{children}</div>
</div>
);
}
@ -40,14 +44,24 @@ function getStyles(theme: GrafanaTheme2) {
borderLeft: `1px solid ${theme.colors.border.weak}`,
}),
header: css({
padding: theme.spacing(1),
padding: theme.spacing(1, 1.5),
fontWeight: theme.typography.fontWeightMedium,
fontSize: theme.typography.h5.fontSize,
display: 'flex',
}),
headerActions: css({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(1),
flexGrow: 1,
justifyContent: 'flex-end',
}),
content: css({
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
overflow: 'auto',
padding: theme.spacing(1, 0),
}),
};
}

@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { AddonAppDefinition } from '../AppChromeService';
export function DeclareAddonApp<T>(props: AddonAppDefinition<T>) {
const { chrome } = useGrafana();
useEffect(() => {
//@ts-ignore
chrome.addAddonApp(props);
return () => {
chrome.removeAddonApp(props.id);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chrome]);
return null;
}

@ -6,6 +6,8 @@ import { FlexItem } from '@grafana/experimental';
import { Box, Card, Stack, useStyles2, Text } from '@grafana/ui';
import GrotCompleted from '@grafana/ui/src/components/EmptyState/grot-completed.svg';
import { AddonBarPane } from './AddonBarPane';
export function HelpPane() {
const styles = useStyles2(getStyles);
// const navIndex = useSelector((s) => s.navIndex);
@ -13,47 +15,49 @@ export function HelpPane() {
// const enrichedHelpNode = helpNode ? enrichHelpItem(helpNode) : undefined;
return (
<Box padding={1} grow={1}>
<Stack direction={'column'} grow={1} gap={1} height={'100%'}>
<Text variant="bodySmall" italic>
Recommended topics
</Text>
<Stack direction={'column'} gap={0.5}>
<Card href="link" isCompact>
<Card.Heading>Dashboard docs</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Adding panels</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Configuring visualizations</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Template variables</Card.Heading>
</Card>
</Stack>
<div className={styles.divider} />
<Text variant="bodySmall" italic>
Other topics
</Text>
<Stack direction={'column'} gap={0.5}>
<Card href="link" isCompact>
<Card.Heading>Support bundles</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Grafana docs</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Keyboard shortcuts</Card.Heading>
</Card>
</Stack>
<FlexItem grow={1} />
<Stack direction={'row'} gap={0.5} alignItems={'center'}>
<SVG src={GrotCompleted} width={40} />
<input autoFocus className={styles.input} type="text" placeholder="Ask a question" />
<AddonBarPane title="Get help">
<Box paddingX={2} grow={1}>
<Stack direction={'column'} grow={1} gap={1} height={'100%'}>
<Text variant="bodySmall" italic>
Recommended topics
</Text>
<Stack direction={'column'} gap={0.5}>
<Card href="link" isCompact>
<Card.Heading>Dashboard docs</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Adding panels</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Configuring visualizations</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Template variables</Card.Heading>
</Card>
</Stack>
<div className={styles.divider} />
<Text variant="bodySmall" italic>
Other topics
</Text>
<Stack direction={'column'} gap={0.5}>
<Card href="link" isCompact>
<Card.Heading>Support bundles</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Grafana docs</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Keyboard shortcuts</Card.Heading>
</Card>
</Stack>
<FlexItem grow={1} />
<Stack direction={'row'} gap={0.5} alignItems={'center'}>
<SVG src={GrotCompleted} width={40} />
<input autoFocus className={styles.input} type="text" placeholder="Ask a question" />
</Stack>
</Stack>
</Stack>
</Box>
</Box>
</AddonBarPane>
);
}

@ -39,7 +39,7 @@ export function AppChrome({ children }: Props) {
const isScopesDashboardsOpen = Boolean(
scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened && !scopesDashboardsState?.isReadOnly
);
const addonBarVisible = state.addonBar || state.kioskMode === KioskMode.Full;
const addonBarVisible = state.addonBarDocked || state.kioskMode === KioskMode.Full;
useMediaQueryChange({
breakpoint: dockedMenuBreakpoint,
@ -104,7 +104,7 @@ export function AppChrome({ children }: Props) {
className={cx(
styles.topNav,
menuDockedAndOpen && styles.topNavMenuDocked,
state.addonBar && styles.topNavWithAddonBar
state.addonBarDocked && styles.topNavWithAddonBar
)}
>
<SingleTopBar
@ -138,7 +138,7 @@ export function AppChrome({ children }: Props) {
>
{children}
</main>
{state.addonBarPane && <AddonBarPane pane={state.addonBarPane} />}
{state.addonBarPane && state.addonBarPane.content}
</div>
</div>
{!state.chromeless && !state.megaMenuDocked && <AppChromeMenu />}

@ -28,14 +28,23 @@ export interface AppChromeState {
title: ReturnToPreviousProps['title'];
href: ReturnToPreviousProps['href'];
};
addonBar?: boolean;
addonBarDocked?: boolean;
addonBarPane?: AddonBarPane;
addonApps: AddonAppDefinition[];
}
export interface AddonAppDefinition<T = {}> {
id: string;
title: string;
icon: string;
component: React.ComponentType<T>;
type: 'context' | 'global';
props: T;
}
export interface AddonBarPane {
title: string;
id: string;
actions?: React.ReactNode;
content: React.ReactNode;
}
@ -64,7 +73,8 @@ export class AppChromeService {
kioskMode: null,
layout: PageLayoutType.Canvas,
returnToPrevious: this.returnToPreviousData,
addonBar: true,
addonBarDocked: true,
addonApps: [],
});
public headerHeightObservable = this.state
@ -184,6 +194,40 @@ export class AppChromeService {
return false;
}
public addAddonApp(app: AddonAppDefinition) {
const current = this.state.getValue();
const addonApps = [...current.addonApps];
addonApps.push(app);
this.update({ addonApps });
}
public removeAddonApp(id: string) {
const current = this.state.getValue();
const addonApps = current.addonApps.filter((app) => app.id !== id);
this.update({ addonApps, addonBarPane: current.addonBarPane?.id === id ? undefined : current.addonBarPane });
}
public openAddon(id: string) {
const current = this.state.getValue();
const addonApp = current.addonApps.find((app) => app.id === id);
if (!addonApp) {
return;
}
if (current.addonBarPane?.id === addonApp.id) {
this.update({ addonBarPane: undefined });
return;
}
this.update({
addonBarPane: {
title: addonApp.title,
id: addonApp.id,
content: <addonApp.component {...addonApp.props} />,
},
});
}
public useState() {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useObservable(this.state, this.state.getValue());

@ -51,6 +51,7 @@ export function HistoryContainer() {
<ToolbarButton
onClick={onToggleShowHistoryDrawer}
iconOnly
tooltip="History"
icon="history"
aria-label={t('nav.history-container.drawer-tittle', 'History')}
/>

@ -82,7 +82,7 @@ export const SingleTopBar = memo(function SingleTopBar({
tooltip="Enable kiosk mode"
/> */}
{!contextSrv.user.isSignedIn && <SignInLink />}
{profileNode && !state.addonBar && (
{profileNode && !state.addonBarDocked && (
<Dropdown overlay={() => <TopNavBarMenu node={profileNode} />} placement="bottom-end">
<ToolbarButton
className={styles.profileButton}

@ -0,0 +1,31 @@
import { Box, Card, FilterInput, Stack, Text } from '@grafana/ui';
import { AddonBarPane } from 'app/core/components/AppChrome/AddonBar/AddonBarPane';
export function DashboardContentPane() {
return (
<AddonBarPane title="Content outline">
<Box paddingX={2} grow={1}>
<Stack direction={'column'} grow={1} gap={1} height={'100%'}>
<FilterInput placeholder="Search" onChange={() => {}} value={''} />
<Text variant="bodySmall" italic>
Sections
</Text>
<Stack direction={'column'} gap={0.5}>
<Card href="link" isCompact>
<Card.Heading>Interpolation modes</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Soft min & max</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Multiple Y-Axes</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>Time series panel & display options</Card.Heading>
</Card>
</Stack>
</Stack>
</Box>
</AddonBarPane>
);
}

@ -64,9 +64,8 @@ export class DashboardEditPane extends SceneObjectBase<DashboardEditPaneState> {
}
public clearSelection() {
const dashboard = getDashboardSceneFor(this);
this.setState({
selectedObject: dashboard.getRef(),
selectedObject: undefined,
selectionContext: {
...this.state.selectionContext,
selected: [],

@ -1,18 +1,21 @@
import { css, cx } from '@emotion/css';
import React, { CSSProperties, useEffect } from 'react';
import { css } from '@emotion/css';
import React, { useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { config, useChromeHeaderHeight } from '@grafana/runtime';
import { useChromeHeaderHeight } from '@grafana/runtime';
import { useSceneObjectState } from '@grafana/scenes';
import { ElementSelectionContext, useStyles2 } from '@grafana/ui';
import { DeclareAddonApp } from 'app/core/components/AppChrome/AddonBar/DeclareAddonApp';
import NativeScrollbar from 'app/core/components/NativeScrollbar';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useSnappingSplitter } from '../panel-edit/splitter/useSnappingSplitter';
import { DashboardScene } from '../scene/DashboardScene';
import { NavToolbarActions } from '../scene/NavToolbarActions';
import { DashboardEditPaneRenderer } from './DashboardEditPane';
import { useEditPaneCollapsed } from './shared';
import { DashboardContentPane } from './DashboardContentPane';
import { DashboardSettingsPane } from './DashboardSettingsPane';
import { SelectedObjectPane } from './SelectedObjectPane';
import { useEditableElement } from './useEditableElement';
interface Props {
dashboard: DashboardScene;
@ -23,83 +26,140 @@ interface Props {
export function DashboardEditPaneSplitter({ dashboard, isEditing, body, controls }: Props) {
const headerHeight = useChromeHeaderHeight();
const { chrome } = useGrafana();
const { editPane } = dashboard.state;
const styles = useStyles2(getStyles, headerHeight ?? 0);
const [isCollapsed, setIsCollapsed] = useEditPaneCollapsed();
const { selectionContext, selectedObject } = useSceneObjectState(editPane, { shouldActivateOrKeepAlive: true });
const editableElement = useEditableElement(selectedObject?.resolve())!;
if (!config.featureToggles.dashboardNewLayouts) {
return (
<NativeScrollbar onSetScrollRef={dashboard.onSetScrollRef}>
<div className={styles.canvasWrappperOld}>
useEffect(() => {
if (!isEditing) {
return;
}
editPane.enableSelection();
return () => {
editPane.disableSelection();
};
}, [editPane, isEditing]);
useEffect(() => {
if (editableElement) {
chrome.openAddon('selected-object');
}
return () => chrome.openAddon('selected-object');
}, [chrome, editableElement]);
return (
<NativeScrollbar onSetScrollRef={dashboard.onSetScrollRef}>
<ElementSelectionContext.Provider value={selectionContext}>
<DeclareAddonApp
id="dashboard-filters"
title="Filters pane"
icon="filter"
type="context"
props={{ dashboard }}
component={DashboardContentPane}
/>
<DeclareAddonApp
id="dashboard-content-outline"
title="Content outline"
icon="list-ui-alt"
type="context"
props={{ dashboard }}
component={DashboardContentPane}
/>
{isEditing && (
<DeclareAddonApp
id="dashboard-settings"
title="Dashboard settings"
icon="cog"
type="context"
props={{ dashboard }}
component={DashboardSettingsPane}
/>
)}
{editableElement && (
<DeclareAddonApp
id="selected-object"
title={editableElement.getTypeName()}
icon="focus-target"
type="context"
props={{ editableElement }}
component={SelectedObjectPane}
/>
)}
<div className={styles.canvasWrappperOld} onPointerDown={() => editPane.clearSelection()}>
<NavToolbarActions dashboard={dashboard} />
<div className={styles.controlsWrapperSticky}>{controls}</div>
<div className={styles.body}>{body}</div>
</div>
</NativeScrollbar>
);
}
const { containerProps, primaryProps, secondaryProps, splitterProps, splitterState, onToggleCollapse } =
useSnappingSplitter({
direction: 'row',
dragPosition: 'end',
initialSize: 0.8,
handleSize: 'sm',
collapsed: isCollapsed,
paneOptions: {
collapseBelowPixels: 250,
snapOpenToPixels: 400,
},
});
</ElementSelectionContext.Provider>
</NativeScrollbar>
);
useEffect(() => {
setIsCollapsed(splitterState.collapsed);
}, [splitterState.collapsed, setIsCollapsed]);
// const { containerProps, primaryProps, secondaryProps, splitterProps, splitterState, onToggleCollapse } =
// useSnappingSplitter({
// direction: 'row',
// dragPosition: 'end',
// initialSize: 0.8,
// handleSize: 'sm',
// collapsed: isCollapsed,
const { selectionContext } = useSceneObjectState(editPane, { shouldActivateOrKeepAlive: true });
const containerStyle: CSSProperties = {};
// paneOptions: {
// collapseBelowPixels: 250,
// snapOpenToPixels: 400,
// },
// });
if (!isEditing) {
primaryProps.style.flexGrow = 1;
primaryProps.style.width = '100%';
primaryProps.style.minWidth = 'unset';
containerStyle.overflow = 'unset';
}
// useEffect(() => {
// setIsCollapsed(splitterState.collapsed);
// }, [splitterState.collapsed, setIsCollapsed]);
const onBodyRef = (ref: HTMLDivElement) => {
dashboard.onSetScrollRef(ref);
};
// const { selectionContext } = useSceneObjectState(editPane, { shouldActivateOrKeepAlive: true });
// const containerStyle: CSSProperties = {};
return (
<div {...containerProps} style={containerStyle}>
<div
{...primaryProps}
className={cx(primaryProps.className, styles.canvasWithSplitter)}
onPointerDown={() => editPane.clearSelection()}
>
<NavToolbarActions dashboard={dashboard} />
<div className={cx(!isEditing && styles.controlsWrapperSticky)}>{controls}</div>
<div className={styles.bodyWrapper}>
<div className={cx(styles.body, isEditing && styles.bodyEditing)} ref={onBodyRef}>
<ElementSelectionContext.Provider value={selectionContext}>{body}</ElementSelectionContext.Provider>
</div>
</div>
</div>
{isEditing && (
<>
<div {...splitterProps} data-edit-pane-splitter={true} />
<div {...secondaryProps} className={cx(secondaryProps.className, styles.editPane)}>
<DashboardEditPaneRenderer
editPane={editPane}
isCollapsed={splitterState.collapsed}
onToggleCollapse={onToggleCollapse}
/>
</div>
</>
)}
</div>
);
// if (!isEditing) {
// primaryProps.style.flexGrow = 1;
// primaryProps.style.width = '100%';
// primaryProps.style.minWidth = 'unset';
// containerStyle.overflow = 'unset';
// }
// const onBodyRef = (ref: HTMLDivElement) => {
// dashboard.onSetScrollRef(ref);
// };
// return (
// <div {...containerProps} style={containerStyle}>
// <div
// {...primaryProps}
// className={cx(primaryProps.className, styles.canvasWithSplitter)}
// onPointerDown={() => editPane.clearSelection()}
// >
// <NavToolbarActions dashboard={dashboard} />
// <div className={cx(!isEditing && styles.controlsWrapperSticky)}>{controls}</div>
// <div className={styles.bodyWrapper}>
// <div className={cx(styles.body, isEditing && styles.bodyEditing)} ref={onBodyRef}>
// <ElementSelectionContext.Provider value={selectionContext}>{body}</ElementSelectionContext.Provider>
// </div>
// </div>
// </div>
// {isEditing && (
// <>
// <div {...splitterProps} data-edit-pane-splitter={true} />
// <div {...secondaryProps} className={cx(secondaryProps.className, styles.editPane)}>
// <DashboardEditPaneRenderer
// editPane={editPane}
// isCollapsed={splitterState.collapsed}
// onToggleCollapse={onToggleCollapse}
// />
// </div>
// </>
// )}
// </div>
// );
}
function getStyles(theme: GrafanaTheme2, headerHeight: number) {
@ -133,7 +193,7 @@ function getStyles(theme: GrafanaTheme2, headerHeight: number) {
gap: '8px',
boxSizing: 'border-box',
flexDirection: 'column',
padding: theme.spacing(0, 2, 2, 2),
padding: theme.spacing(0.2, 2, 2, 2),
}),
bodyEditing: css({
position: 'absolute',

@ -22,7 +22,7 @@ export class DashboardEditableElement implements EditableDashboardElement {
const dashboardOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: 'Dashboard options',
id: 'dashboard-options',
id: '',
isOpenDefault: true,
})
.addItem(

@ -0,0 +1,24 @@
import { Box, Card, FilterInput, Stack, Text } from '@grafana/ui';
import { AddonBarPane } from 'app/core/components/AppChrome/AddonBar/AddonBarPane';
export function DashboardContentPane() {
return (
<AddonBarPane title="Filters">
<Box paddingX={2} grow={1}>
<Stack direction={'column'} grow={1} gap={1} height={'100%'}>
<Text variant="bodySmall" italic>
Sections
</Text>
<Stack direction={'column'} gap={0.5}>
<Card href="link" isCompact>
<Card.Heading>job = server</Card.Heading>
</Card>
<Card href="link" isCompact>
<Card.Heading>cluster = US</Card.Heading>
</Card>
</Stack>
</Stack>
</Box>
</AddonBarPane>
);
}

@ -0,0 +1,9 @@
import { DashboardScene } from '../scene/DashboardScene';
import { ElementEditPane } from './ElementEditPane';
import { useEditableElement } from './useEditableElement';
export function DashboardSettingsPane({ dashboard }: { dashboard: DashboardScene }) {
const editableElement = useEditableElement(dashboard)!;
return <ElementEditPane element={editableElement} key={editableElement.getTypeName()} />;
}

@ -1,8 +1,8 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack, useStyles2 } from '@grafana/ui';
import { OptionsPaneCategory } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategory';
import { Stack } from '@grafana/ui';
import { AddonBarPane } from 'app/core/components/AppChrome/AddonBar/AddonBarPane';
import { EditableDashboardElement } from '../scene/types';
@ -12,22 +12,13 @@ export interface Props {
export function ElementEditPane({ element }: Props) {
const categories = element.useEditPaneOptions();
const styles = useStyles2(getStyles);
return (
<Stack direction="column" gap={0}>
{element.renderActions && (
<OptionsPaneCategory
id="selected-item"
title={element.getTypeName()}
isOpenDefault={true}
className={styles.noBorderTop}
>
<div className={styles.actionsBox}>{element.renderActions()}</div>
</OptionsPaneCategory>
)}
{categories.map((cat) => cat.render())}
</Stack>
<AddonBarPane title={element.getTypeName()} actions={element.renderActions && element.renderActions()}>
<Stack direction="column" gap={0}>
{categories.map((cat) => cat.render())}
</Stack>
</AddonBarPane>
);
}

@ -0,0 +1,7 @@
import { EditableDashboardElement } from '../scene/types';
import { ElementEditPane } from './ElementEditPane';
export function SelectedObjectPane({ editableElement }: { editableElement: EditableDashboardElement }) {
return <ElementEditPane element={editableElement} key={editableElement.getTypeName()} />;
}

@ -27,7 +27,7 @@ export class VizPanelEditableElement implements EditableDashboardElement {
const panelOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: 'Panel options',
id: 'panel-options',
id: '',
isOpenDefault: true,
})
.addItem(

@ -31,7 +31,7 @@ export class RowItem extends SceneObjectBase<RowItemState> implements LayoutPare
const rowOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: 'Row options',
id: 'row-options',
id: '',
isOpenDefault: true,
})
.addItem(

@ -16,6 +16,7 @@ export interface OptionsPaneCategoryDescriptorProps {
itemsCount?: number;
customRender?: () => React.ReactNode;
sandboxId?: string;
topLevel?: boolean;
}
/**
@ -60,7 +61,11 @@ export class OptionsPaneCategoryDescriptor {
}
if (this.props.id === '') {
return <Box padding={2}>{this.items.map((item) => item.render(searchQuery))}</Box>;
return (
<Box paddingX={2} paddingTop={0} paddingBottom={1}>
{this.items.map((item) => item.render(searchQuery))}
</Box>
);
}
return (

Loading…
Cancel
Save