chore: alex - header + footer + splitter padding + various styles updates

This commit is contained in:
Alex Spencer
2025-12-02 12:28:34 -08:00
parent b489c4360a
commit e60c5a9342
6 changed files with 149 additions and 51 deletions
@@ -90,6 +90,7 @@ const getStyles = (theme: GrafanaTheme2) => {
height: '100%',
width: '100%',
background: theme.colors.background.primary,
border: `1px solid ${theme.colors.border.weak}`,
}),
emptyState: css({
display: 'flex',
@@ -265,7 +265,7 @@ export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps)
return (
<div className={styles.header}>
<Stack justifyContent="space-between" alignItems="center" gap={2}>
<div className={styles.headerContent}>
{/* Left side: Icon, Datasource, Name */}
<Stack gap={1} alignItems="center" grow={1} minWidth={0}>
<Icon name={config.icon} className={styles.icon} />
@@ -358,7 +358,7 @@ export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps)
</Dropdown>
</Stack>
)}
</Stack>
</div>
</div>
);
};
@@ -366,10 +366,18 @@ export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps)
const getStyles = (theme: GrafanaTheme2, config: { color: string }) => {
return {
header: css({
padding: theme.spacing(1.5, 2),
padding: theme.spacing(0.5),
borderLeft: `4px solid ${config.color}`,
borderBottom: `1px solid ${theme.colors.border.weak}`,
background: theme.colors.background.secondary,
minHeight: '41px',
}),
headerContent: css({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: theme.spacing(2),
height: '100%',
}),
icon: css({
color: theme.colors.text.secondary,
@@ -473,7 +473,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
<div
{...splitterProps}
className={cx(splitterProps.className, styles.splitter)}
style={{ ...splitterProps.style, width: 0 }}
style={{ ...splitterProps.style, width: '16px' }}
/>
<div {...secondaryProps}>
<DetailView selectedItem={selectedItem} panel={panel} tabs={tabs} />
@@ -517,24 +517,12 @@ function getStyles(theme: GrafanaTheme2) {
flex: 1,
minHeight: 0,
background: theme.colors.background.primary,
border: `1px solid ${theme.colors.border.weak}`,
borderLeft: 'none',
borderBottom: 'none',
borderTopRightRadius: theme.shape.radius.default,
overflow: 'hidden',
}),
splitter: css({
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
left: '50%',
top: 0,
bottom: 0,
width: '1px',
background: theme.colors.border.weak,
transform: 'translateX(-50%)',
},
background: theme.colors.background.canvas,
cursor: 'col-resize',
}),
};
}
@@ -353,7 +353,7 @@ export function QueryDetailView({ panel, query, queryIndex }: QueryDetailViewPro
<div className={styles.footer}>
{renderCollapsedText()}
<Button
size="xs"
size="sm"
icon={showOptions ? 'angle-right' : 'angle-left'}
fill="text"
onClick={() => setShowOptions(!showOptions)}
@@ -408,10 +408,11 @@ const getStyles = (theme: GrafanaTheme2) => {
}),
queryOperationRow: css({
marginBottom: '0 !important', // need to beat specificty in the underling component
minHeight: 'calc(100% - 49px)', // 49px for the footer
minHeight: 'calc(100% - 32px)', // 32px for the footer
overflow: 'scroll',
}),
footer: css({
height: '32px',
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
@@ -419,7 +420,7 @@ const getStyles = (theme: GrafanaTheme2) => {
position: 'sticky',
bottom: 0,
zIndex: theme.zIndex.navbarFixed,
padding: theme.spacing(1, 2),
padding: theme.spacing(0.5, 2),
background: theme.colors.background.secondary,
}),
optionsColumn: css({
@@ -2,8 +2,9 @@ import { css } from '@emotion/css';
import { memo, useMemo } from 'react';
import { DataTransformerConfig, GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { SceneDataQuery } from '@grafana/scenes';
import { ScrollContainer, Stack, useStyles2 } from '@grafana/ui';
import { Icon, ScrollContainer, Stack, useStyles2 } from '@grafana/ui';
import { ExpressionQueryType } from 'app/features/expressions/types';
import { AddDataItemMenu } from './AddDataItemMenu';
@@ -122,32 +123,69 @@ export const QueryTransformList = memo(
}
};
const stats = useMemo(() => {
const totalCards = items.length;
const queries = items.filter((item) => item.type === 'query' || item.type === 'expression');
const hiddenQueries = queries.filter((item) => 'hide' in item.data && item.data.hide);
const visibleQueries = queries.length - hiddenQueries.length;
return {
totalCards,
visibleQueries,
hiddenQueries: hiddenQueries.length,
};
}, [items]);
return (
<div className={styles.container}>
<div className={styles.header}>
<Stack justifyContent="space-between" alignItems="center" gap={2}>
<span className={styles.headerTitle}>
{t('dashboard-scene.query-transform-list.header', 'Pipeline flow')}
</span>
</Stack>
</div>
<ConnectionLines connections={visibleConnections} />
<ScrollContainer data-scrollcontainer>
<div className={styles.content}>
<Stack direction="column" gap={2}>
{items.map((item) => (
<QueryTransformCard
key={item.id}
item={item.data}
type={item.type}
index={item.index}
isSelected={selectedId === item.id}
onClick={() => onSelect(item.id)}
{...getHandlers(item)}
/>
))}
<div className={styles.scrollWrapper}>
<ScrollContainer data-scrollcontainer>
<div className={styles.content}>
<Stack direction="column" gap={2}>
{items.map((item) => (
<QueryTransformCard
key={item.id}
item={item.data}
type={item.type}
index={item.index}
isSelected={selectedId === item.id}
onClick={() => onSelect(item.id)}
{...getHandlers(item)}
/>
))}
<AddDataItemMenu
onAddQuery={onAddQuery}
onAddTransform={onAddTransform}
onAddExpression={onAddExpression}
/>
</Stack>
</div>
</ScrollContainer>
<AddDataItemMenu
onAddQuery={onAddQuery}
onAddTransform={onAddTransform}
onAddExpression={onAddExpression}
/>
</Stack>
</div>
</ScrollContainer>
</div>
<div className={styles.footer}>
<Stack direction="row" gap={1.5}>
<span className={styles.footerStat}>
{stats.totalCards} {t('dashboard-scene.query-transform-list.nodes-total', 'nodes total')}
</span>
<span className={styles.footerStat}>
<Icon size="xs" name="eye" />
{t('dashboard-scene.query-transform-list.visible-queries', 'Visible')}: {stats.visibleQueries}
</span>
<span className={styles.footerStat}>
<Icon size="xs" name="eye-slash" />
{t('dashboard-scene.query-transform-list.hidden-queries', 'Hidden')}: {stats.hiddenQueries}
</span>
</Stack>
</div>
</div>
);
}
@@ -156,6 +194,17 @@ export const QueryTransformList = memo(
QueryTransformList.displayName = 'QueryTransformList';
const getStyles = (theme: GrafanaTheme2) => {
const headerHeight = 41;
const footerHeight = 32;
const monoFont = "'CommitMono', monospace";
const barBase = {
padding: theme.spacing(0.5, 2),
background: theme.colors.background.secondary,
display: 'flex',
alignItems: 'center',
flexShrink: 0,
};
return {
container: css({
position: 'relative',
@@ -164,12 +213,44 @@ const getStyles = (theme: GrafanaTheme2) => {
height: '100%',
width: '100%',
overflow: 'hidden',
border: `1px solid ${theme.colors.border.weak}`,
}),
header: css({
...barBase,
height: headerHeight,
borderBottom: `1px solid ${theme.colors.border.weak}`,
}),
headerTitle: css({
fontFamily: monoFont,
textTransform: 'uppercase',
color: theme.colors.text.primary,
}),
scrollWrapper: css({
flex: 1,
minHeight: 0,
position: 'relative',
}),
content: css({
position: 'relative',
padding: theme.spacing(2),
paddingRight: theme.spacing(6), // Extra space for the connection line
zIndex: 1,
paddingRight: theme.spacing(6),
}),
footer: css({
...barBase,
height: footerHeight,
borderTop: `1px solid ${theme.colors.border.weak}`,
gap: theme.spacing(1),
position: 'relative',
zIndex: 20,
}),
footerStat: css({
fontFamily: monoFont,
fontSize: '10px',
color: theme.colors.text.primary,
textTransform: 'uppercase',
letterSpacing: '0.05em',
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5),
}),
};
};
+23 -4
View File
@@ -5976,6 +5976,15 @@
"description-label": {
"description": "Description"
},
"detail-view-header": {
"actions": "Actions",
"duplicate-query": "Duplicate",
"edit-query-name": "Edit query name",
"hide-response": "Hide response",
"remove-query": "Remove",
"run-query": "RUN QUERY",
"show-response": "Show response"
},
"edit-link-view": {
"edit-link-page-nav": {
"text": {
@@ -6253,7 +6262,7 @@
"query-detail-view": {
"loading": "Loading data source...",
"no-editor": "This data source does not have a query editor",
"select-datasource": "Select data source"
"options": "Options"
},
"query-editor": {
"query": "Query"
@@ -6265,6 +6274,12 @@
"remove-transform": "Remove transformation",
"show-response": "Show response"
},
"query-transform-list": {
"header": "Pipeline flow",
"hidden-queries": "Hidden",
"nodes-total": "nodes total",
"visible-queries": "Visible"
},
"query-variable-editor-form": {
"description-examples": "Named capture groups can be used to separate the display text and value (<1>see examples</1> ).",
"description-optional": "Optional, if you want to extract part of a series name or metric node segment.",
@@ -12320,10 +12335,14 @@
"title-data-source-help": "Data source help"
},
"query-group-options-editor": {
"collapsed-interval": "Interval = {{intervalDesc}}",
"collapsed-max-data-points": "MD = {{mdDesc}}",
"collapsed-cache-timeout-label": "Cache timeout",
"collapsed-cache-ttl-label": "Cache TTL",
"collapsed-interval-label": "Interval",
"collapsed-max-data-points-label": "Max data points",
"collapsed-min-interval-label": "Min interval",
"collapsed-relative-time-label": "Relative time",
"collapsed-time-shift-label": "Time shift",
"hide-time-info": "Hide time info",
"Query options-title-query-options": "Query options",
"relative-time": "Relative time",
"relative-time-tooltip": "Overrides the relative time range for individual panels, which causes them to be different than what is selected in the dashboard time picker in the top-right corner of the dashboard. For example to configure the Last 5 minutes the Relative time should be <1>{{relativeFrom}}</1> and <4>{{relativeTo}}</4>, or variables like <6>{{variable}}</6>.",
"render-cache-timeout-option": {