|
|
|
@ -4,7 +4,7 @@ import { useCallback, useState } from 'react'; |
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data'; |
|
|
|
|
import { selectors } from '@grafana/e2e-selectors'; |
|
|
|
|
import { SceneComponentProps } from '@grafana/scenes'; |
|
|
|
|
import { clearButtonStyles, Icon, useStyles2 } from '@grafana/ui'; |
|
|
|
|
import { clearButtonStyles, Icon, Tooltip, useStyles2 } from '@grafana/ui'; |
|
|
|
|
import { t } from 'app/core/internationalization'; |
|
|
|
|
|
|
|
|
|
import { useIsClone } from '../../utils/clone'; |
|
|
|
@ -19,25 +19,24 @@ import { RowItem } from './RowItem'; |
|
|
|
|
import { RowItemMenu } from './RowItemMenu'; |
|
|
|
|
|
|
|
|
|
export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) { |
|
|
|
|
const { layout, isCollapsed, height = 'min', isHeaderHidden } = model.useState(); |
|
|
|
|
const { layout, isCollapsed, fillScreen, isHeaderHidden } = model.useState(); |
|
|
|
|
const isClone = useIsClone(model); |
|
|
|
|
const { isEditing, showHiddenElements } = useDashboardState(model); |
|
|
|
|
const { isEditing } = useDashboardState(model); |
|
|
|
|
const isConditionallyHidden = useIsConditionallyHidden(model); |
|
|
|
|
const { isSelected, onSelect, isSelectable } = useElementSelectionScene(model); |
|
|
|
|
const title = useInterpolatedTitle(model); |
|
|
|
|
const styles = useStyles2(getStyles); |
|
|
|
|
const clearStyles = useStyles2(clearButtonStyles); |
|
|
|
|
|
|
|
|
|
const shouldGrow = !isCollapsed && height === 'expand'; |
|
|
|
|
const isHiddenButVisibleElement = showHiddenElements && isConditionallyHidden; |
|
|
|
|
const isHiddenButVisibleHeader = showHiddenElements && isHeaderHidden; |
|
|
|
|
const shouldGrow = !isCollapsed && fillScreen; |
|
|
|
|
const isHidden = isConditionallyHidden && !isEditing; |
|
|
|
|
|
|
|
|
|
// Highlight the full row when hovering over header
|
|
|
|
|
const [selectableHighlight, setSelectableHighlight] = useState(false); |
|
|
|
|
const onHeaderEnter = useCallback(() => setSelectableHighlight(true), []); |
|
|
|
|
const onHeaderLeave = useCallback(() => setSelectableHighlight(false), []); |
|
|
|
|
|
|
|
|
|
if (isConditionallyHidden && !showHiddenElements) { |
|
|
|
|
if (isHidden) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -49,19 +48,24 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) { |
|
|
|
|
isEditing && isCollapsed && styles.wrapperEditingCollapsed, |
|
|
|
|
isCollapsed && styles.wrapperCollapsed, |
|
|
|
|
shouldGrow && styles.wrapperGrow, |
|
|
|
|
isHiddenButVisibleElement && 'dashboard-visible-hidden-element', |
|
|
|
|
isConditionallyHidden && 'dashboard-visible-hidden-element', |
|
|
|
|
!isClone && isSelected && 'dashboard-selected-element', |
|
|
|
|
!isClone && !isSelected && selectableHighlight && 'dashboard-selectable-element' |
|
|
|
|
)} |
|
|
|
|
onPointerDown={onSelect} |
|
|
|
|
onPointerDown={(e) => { |
|
|
|
|
// If we selected and are clicking a button inside row header then don't de-select row
|
|
|
|
|
if (isSelected && e.target instanceof Element && e.target.closest('button')) { |
|
|
|
|
// Stop propagation otherwise dashboaed level onPointerDown will de-select row
|
|
|
|
|
e.stopPropagation(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onSelect?.(e); |
|
|
|
|
}} |
|
|
|
|
> |
|
|
|
|
{(!isHeaderHidden || (isEditing && showHiddenElements)) && ( |
|
|
|
|
{(!isHeaderHidden || isEditing) && ( |
|
|
|
|
<div |
|
|
|
|
className={cx( |
|
|
|
|
isHiddenButVisibleHeader && 'dashboard-visible-hidden-element', |
|
|
|
|
styles.rowHeader, |
|
|
|
|
'dashboard-row-header' |
|
|
|
|
)} |
|
|
|
|
className={cx(isHeaderHidden && 'dashboard-visible-hidden-element', styles.rowHeader, 'dashboard-row-header')} |
|
|
|
|
onMouseEnter={isSelectable ? onHeaderEnter : undefined} |
|
|
|
|
onMouseLeave={isSelectable ? onHeaderLeave : undefined} |
|
|
|
|
> |
|
|
|
@ -76,8 +80,15 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) { |
|
|
|
|
data-testid={selectors.components.DashboardRow.title(title!)} |
|
|
|
|
> |
|
|
|
|
<Icon name={isCollapsed ? 'angle-right' : 'angle-down'} /> |
|
|
|
|
<span className={styles.rowTitle} role="heading"> |
|
|
|
|
<span className={cx(styles.rowTitle, isHeaderHidden && styles.rowTitleHidden)} role="heading"> |
|
|
|
|
{title} |
|
|
|
|
{isHeaderHidden && ( |
|
|
|
|
<Tooltip |
|
|
|
|
content={t('dashboard.rows-layout.header-hidden-tooltip', 'Row header only visible in edit mode')} |
|
|
|
|
> |
|
|
|
|
<Icon name="eye-slash" /> |
|
|
|
|
</Tooltip> |
|
|
|
|
)} |
|
|
|
|
</span> |
|
|
|
|
</button> |
|
|
|
|
{!isClone && isEditing && <RowItemMenu model={model} />} |
|
|
|
@ -108,6 +119,9 @@ function getStyles(theme: GrafanaTheme2) { |
|
|
|
|
gap: theme.spacing(1), |
|
|
|
|
}), |
|
|
|
|
rowTitle: css({ |
|
|
|
|
display: 'flex', |
|
|
|
|
alignItems: 'center', |
|
|
|
|
gap: theme.spacing(2), |
|
|
|
|
fontSize: theme.typography.h5.fontSize, |
|
|
|
|
fontWeight: theme.typography.fontWeightMedium, |
|
|
|
|
whiteSpace: 'nowrap', |
|
|
|
@ -117,6 +131,9 @@ function getStyles(theme: GrafanaTheme2) { |
|
|
|
|
flexGrow: 1, |
|
|
|
|
minWidth: 0, |
|
|
|
|
}), |
|
|
|
|
rowTitleHidden: css({ |
|
|
|
|
textDecoration: 'line-through', |
|
|
|
|
}), |
|
|
|
|
wrapper: css({ |
|
|
|
|
display: 'flex', |
|
|
|
|
flexDirection: 'column', |
|
|
|
|