diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 3570094ff31..685458d40ad 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -252,6 +252,9 @@ export const availableIconsIndex = { unlock: true, upload: true, user: true, + ban: true, + window: true, + 'window-grid': true, 'users-alt': true, 'user-arrows': true, 'vertical-align-bottom': true, diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index d615ca4d696..918896cd79c 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -125,6 +125,7 @@ const getStyles = (theme: GrafanaTheme2) => { }), fullWidth: css({ display: 'flex', + flexGrow: 1, }), icon: css({ marginRight: '6px', diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx index 57f574f5470..431dfed0d1b 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx @@ -48,7 +48,7 @@ export class DashboardEditableElement implements EditableDashboardElement { const layoutCategory = useLayoutCategory(body); - return [dashboardOptions, layoutCategory]; + return [dashboardOptions, ...layoutCategory]; } public renderActions(): ReactNode { diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index fc2f48fa6f1..6603bb3e01a 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -72,6 +72,7 @@ export class DefaultGridLayoutManager id: 'GridLayout', createFromLayout: DefaultGridLayoutManager.createFromLayout, isGridLayout: true, + icon: 'window-grid', }; public serialize(): DashboardV2Spec['layout'] { diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index 46459c88cc2..bcebddc5ac2 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -56,6 +56,7 @@ export class AutoGridLayoutManager id: 'AutoGridLayout', createFromLayout: AutoGridLayoutManager.createFromLayout, isGridLayout: true, + icon: 'apps', }; public serialize(): DashboardV2Spec['layout'] { diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx index f195226097f..6012eae70cb 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx @@ -64,7 +64,7 @@ export function getEditOptions(model: RowItem): OptionsPaneCategoryDescriptor[] const layoutCategory = useLayoutCategory(layout); - const editOptions = [rowCategory, layoutCategory, repeatCategory]; + const editOptions = [rowCategory, ...layoutCategory, repeatCategory]; const conditionalRenderingCategory = useMemo( () => useConditionalRenderingEditor(model.state.conditionalRendering), diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index fb4240520af..4a99a719e5e 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -43,6 +43,7 @@ export class RowsLayoutManager extends SceneObjectBase i id: 'RowsLayout', createFromLayout: RowsLayoutManager.createFromLayout, isGridLayout: false, + icon: 'list-ul', }; public serialize(): DashboardV2Spec['layout'] { 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 4754b3c2d15..35d864a3b38 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx @@ -27,7 +27,7 @@ export function getEditOptions(model: TabItem): OptionsPaneCategoryDescriptor[] const layoutCategory = useLayoutCategory(layout); - const editOptions = [tabCategory, layoutCategory]; + const editOptions = [tabCategory, ...layoutCategory]; const conditionalRenderingCategory = useMemo( () => useConditionalRenderingEditor(model.state.conditionalRendering), diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index b73dc7eda73..5a22c6aaabf 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -45,6 +45,7 @@ export class TabsLayoutManager extends SceneObjectBase i id: 'TabsLayout', createFromLayout: TabsLayoutManager.createFromLayout, isGridLayout: false, + icon: 'window', }; public serialize(): DashboardV2Spec['layout'] { diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx index 03003e3f93a..8351dc28cf7 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx +++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx @@ -1,8 +1,6 @@ -import { css, cx } from '@emotion/css'; import { useCallback, useMemo } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; -import { RadioButtonDot, Stack, useStyles2, Text } from '@grafana/ui'; +import { RadioButtonGroup, Box } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -20,7 +18,6 @@ export interface Props { export function DashboardLayoutSelector({ layoutManager }: Props) { const isGridLayout = layoutManager.descriptor.isGridLayout; const options = layoutRegistry.list().filter((layout) => layout.isGridLayout === isGridLayout); - const styles = useStyles2(getStyles); const onChangeLayout = useCallback( (newLayout: LayoutRegistryItem) => { @@ -33,157 +30,36 @@ export function DashboardLayoutSelector({ layoutManager }: Props) { [layoutManager] ); - return ( -
- {options.map((opt) => { - switch (opt.id) { - case 'RowsLayout': - return ( - -
- {/* eslint-disable-next-line @grafana/no-untranslated-strings */} -
⌄   .-.-.-.-.-
- - - - {/* eslint-disable-next-line @grafana/no-untranslated-strings */} -
⌄   .-.-.-.-.-
- - - -
-
- ); - case 'TabsLayout': - return ( - - -
- {/* eslint-disable-next-line @grafana/no-untranslated-strings */} -
-.-.-
- {/* eslint-disable-next-line @grafana/no-untranslated-strings */} -
-.-.-
- {/* eslint-disable-next-line @grafana/no-untranslated-strings */} -
-.-.-
-
-
- - -
-
-
- ); - case 'AutoGridLayout': - return ( - -
- - - - -
-
- ); - case 'GridLayout': - default: - return ( - -
- -
- - -
- - -
-
- ); - } - })} -
- ); -} - -interface LayoutRadioButtonProps { - item: LayoutRegistryItem; - isSelected: boolean; - onSelect: (item: LayoutRegistryItem) => void; - children: React.ReactNode; -} - -function LayoutRadioButton({ item, isSelected, children, onSelect }: LayoutRadioButtonProps) { - const styles = useStyles2(getStyles); + const radioOptions = options.map((opt) => ({ + value: opt, + label: opt.name, + icon: opt.icon, + description: opt.description, + })); return ( - // This outer div is just so that the radio dot can be outside the - // label (as the RadioButtonDot has a label element and they can't nest) -
- -
- } - onChange={() => onSelect(item)} - checked={isSelected} - /> -
-
+ + + ); } - -function GridCell({ colSpan = 1 }: { colSpan?: number }) { - const styles = useStyles2(getStyles); - - return
; -} - export function useLayoutCategory(layoutManager: DashboardLayoutManager) { return useMemo(() => { - const categoryName = layoutManager.descriptor.isGridLayout - ? t('dashboard.layout.common.grid', 'Grid') - : t('dashboard.layout.common.layout', 'Layout'); + const isGridLayout = layoutManager.descriptor.isGridLayout; - const layoutCategory = new OptionsPaneCategoryDescriptor({ - title: categoryName, - id: 'layout-options', - isOpenDefault: true, + const groupLayout = new OptionsPaneCategoryDescriptor({ + title: t('dashboard.layout.common.group-layout', 'Group layout'), + id: 'group-layout-category', + isOpenDefault: false, }); - layoutCategory.addItem( + const gridLayout = new OptionsPaneCategoryDescriptor({ + title: t('dashboard.layout.common.panel-layout', 'Panel layout'), + id: 'grid-layout-category', + isOpenDefault: false, + }); + + gridLayout.addItem( new OptionsPaneItemDescriptor({ title: '', skipField: true, @@ -191,109 +67,32 @@ export function useLayoutCategory(layoutManager: DashboardLayoutManager) { }) ); + if (isGridLayout) { + groupLayout.props.disabledText = t( + 'dashboard.layout.common.group-layout-disabled', + 'No groups exists on this level' + ); + } else { + groupLayout.addItem( + new OptionsPaneItemDescriptor({ + title: '', + skipField: true, + render: () => , + }) + ); + + gridLayout.props.disabledText = t( + 'dashboard.layout.common.panel-layout-disabled', + 'Select a row or tab to change panel layout options' + ); + } + if (layoutManager.getOptions) { for (const option of layoutManager.getOptions()) { - layoutCategory.addItem(option); + gridLayout.addItem(option); } } - return layoutCategory; + return [groupLayout, gridLayout]; }, [layoutManager]); } - -const getStyles = (theme: GrafanaTheme2) => { - return { - radioButtonOuter: css({ - position: 'relative', - }), - radioDot: css({ - position: 'absolute', - top: theme.spacing(0.5), - right: theme.spacing(0), - }), - radioGroup: css({ - backgroundColor: theme.colors.background.primary, - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), - marginBottom: theme.spacing(2), - }), - radioButton: css({ - alignItems: 'flex-start', - gap: theme.spacing(1.5), - padding: theme.spacing(1), - border: `1px solid ${theme.colors.border.weak}`, - cursor: 'pointer', - borderRadius: theme.shape.radius.default, - display: 'grid', - gridTemplateColumns: `80px 1fr`, - gridTemplateRows: '70px', - }), - radioButtonActive: css({ - border: `1px solid ${theme.colors.primary.border}`, - }), - gridCell: css({ - backgroundColor: theme.colors.background.secondary, - border: `1px solid ${theme.colors.border.medium}`, - }), - tab: css({ - width: theme.spacing(2), - height: theme.spacing(1), - fontSize: '5px', - display: 'flex', - alignItems: 'center', - position: 'relative', - justifyContent: 'center', - }), - tabActive: css({ - '&:before': { - content: '" "', - position: 'absolute', - height: 1, - bottom: 0, - left: 0, - right: 0, - background: theme.colors.gradients.brandHorizontal, - }, - }), - tabsBar: css({ - display: 'flex', - gap: theme.spacing(0.5), - borderBottom: `1px solid ${theme.colors.border.medium}`, - }), - rowsLayoutViz: css({ - display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)', - gridTemplateRows: '10px 1fr 10px 1fr', - gap: '4px', - height: '100%', - }), - tabsVizTabContent: css({ - display: 'grid', - gridTemplateColumns: '1fr 1fr', - gridTemplateRows: '1fr', - gap: '4px', - flexGrow: 1, - }), - autoGridViz: css({ - display: 'grid', - gridTemplateColumns: 'repeat(2, 1fr)', - gridTemplateRows: 'repeat(2, 1fr)', - gap: '4px', - height: '100%', - }), - customGridViz: css({ - display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)', - gridTemplateRows: 'repeat(2, 1fr)', - gap: '4px', - height: '100%', - }), - customGridVizInner: css({ - display: 'grid', - gridTemplateColumns: 'repeat(1, 1fr)', - gridTemplateRows: 'repeat(2, 1fr)', - gap: '4px', - }), - }; -}; diff --git a/public/app/features/dashboard-scene/scene/types/LayoutRegistryItem.ts b/public/app/features/dashboard-scene/scene/types/LayoutRegistryItem.ts index cd40b7f3e6d..360e37f7a32 100644 --- a/public/app/features/dashboard-scene/scene/types/LayoutRegistryItem.ts +++ b/public/app/features/dashboard-scene/scene/types/LayoutRegistryItem.ts @@ -1,4 +1,4 @@ -import { RegistryItem } from '@grafana/data'; +import { IconName, RegistryItem } from '@grafana/data'; import { DashboardLayoutManager } from './DashboardLayoutManager'; @@ -16,4 +16,9 @@ export interface LayoutRegistryItem extends RegistryItem { * Is grid layout (that contains panels) */ isGridLayout: boolean; + + /** + * icon name + */ + icon: IconName; } diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx index a3717c4802c..58bf9cc3ad2 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx @@ -5,7 +5,7 @@ import { useLocalStorage } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Button, Counter, useStyles2 } from '@grafana/ui'; +import { Button, Counter, Icon, Tooltip, useStyles2 } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers'; @@ -21,6 +21,10 @@ export interface OptionsPaneCategoryProps { isNested?: boolean; children: ReactNode; sandboxId?: string; + /** + * When set will disable category and show tooltip with disabledText on hover + */ + disabledText?: string; } const CATEGORY_PARAM_NAME = 'showCategory' as const; @@ -37,6 +41,7 @@ export const OptionsPaneCategory = React.memo( itemsCount, isNested = false, sandboxId, + disabledText, }: OptionsPaneCategoryProps) => { const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), { isExpanded: isOpenDefault, @@ -96,6 +101,29 @@ export const OptionsPaneCategory = React.memo( [styles.bodyNested]: isNested, }); + /** + * Disabled categories just show the disabled header and icon + */ + if (disabledText) { + return ( +
+ +
+
+ {renderTitle(isExpanded)} +
+ +
+
+
+ ); + } + return (
{renderTitle(isExpanded)} -