Skip to content

Commit db11687

Browse files
ThomThomsonMaja Grubic
andcommitted
[Time to Visualize] Panel Title Fixes (#78365)
* [Dashboard][Embeddable] Add placeholder title to embeddable panel, stored 'show panel title' prop in embeddable input. Co-authored-by: Maja Grubic <maja.grubic@elastic.co>
1 parent d0d5e24 commit db11687

8 files changed

Lines changed: 118 additions & 99 deletions

File tree

examples/embeddable_examples/public/book/book_embeddable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class BookEmbeddable
8989
} else {
9090
this.updateOutput({
9191
attributes: this.attributes,
92+
defaultTitle: this.attributes.title,
9293
hasMatch: getHasMatch(this.input.search, this.attributes),
9394
});
9495
}
@@ -125,6 +126,7 @@ export class BookEmbeddable
125126

126127
this.updateOutput({
127128
attributes: this.attributes,
129+
defaultTitle: this.attributes.title,
128130
hasMatch: getHasMatch(this.input.search, this.attributes),
129131
});
130132
}

src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
.embPanel__titleText {
6161
@include euiTextTruncate;
6262
}
63+
64+
.embPanel__placeholderTitleText {
65+
@include euiTextTruncate;
66+
font-weight: $euiFontWeightRegular;
67+
color: $euiColorMediumShade;
68+
}
6369
}
6470

6571
.embPanel__dragger:not(.embPanel__title) {
@@ -159,4 +165,4 @@
159165
pointer-events: none;
160166
filter: grayscale(100%);
161167
filter: gray;
162-
}
168+
}

src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ import {
3232
EmbeddableContext,
3333
contextMenuTrigger,
3434
} from '../triggers';
35-
import { IEmbeddable, EmbeddableOutput, EmbeddableError } from '../embeddables/i_embeddable';
35+
import {
36+
IEmbeddable,
37+
EmbeddableOutput,
38+
EmbeddableError,
39+
EmbeddableInput,
40+
} from '../embeddables/i_embeddable';
3641
import { ViewMode } from '../types';
3742

3843
import { RemovePanelAction } from './panel_header/panel_actions';
@@ -55,7 +60,7 @@ const removeById = (disabledActions: string[]) => ({ id }: { id: string }) =>
5560
disabledActions.indexOf(id) === -1;
5661

5762
interface Props {
58-
embeddable: IEmbeddable<any, any>;
63+
embeddable: IEmbeddable<EmbeddableInput, EmbeddableOutput>;
5964
getActions: UiActionsService['getTriggerCompatibleActions'];
6065
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
6166
getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
@@ -72,7 +77,7 @@ interface State {
7277
panels: EuiContextMenuPanelDescriptor[];
7378
focusedPanelIndex?: string;
7479
viewMode: ViewMode;
75-
hidePanelTitles: boolean;
80+
hidePanelTitle: boolean;
7681
closeContextMenu: boolean;
7782
badges: Array<Action<EmbeddableContext>>;
7883
notifications: Array<Action<EmbeddableContext>>;
@@ -90,17 +95,15 @@ export class EmbeddablePanel extends React.Component<Props, State> {
9095
constructor(props: Props) {
9196
super(props);
9297
const { embeddable } = this.props;
93-
const viewMode = embeddable.getInput().viewMode
94-
? embeddable.getInput().viewMode
95-
: ViewMode.EDIT;
96-
const hidePanelTitles = embeddable.parent
97-
? Boolean(embeddable.parent.getInput().hidePanelTitles)
98-
: false;
98+
const viewMode = embeddable.getInput().viewMode ?? ViewMode.EDIT;
99+
const hidePanelTitle =
100+
Boolean(embeddable.parent?.getInput()?.hidePanelTitles) ||
101+
Boolean(embeddable.getInput()?.hidePanelTitles);
99102

100103
this.state = {
101104
panels: [],
102105
viewMode,
103-
hidePanelTitles,
106+
hidePanelTitle,
104107
closeContextMenu: false,
105108
badges: [],
106109
notifications: [],
@@ -150,9 +153,7 @@ export class EmbeddablePanel extends React.Component<Props, State> {
150153
embeddable.getInput$().subscribe(async () => {
151154
if (this.mounted) {
152155
this.setState({
153-
viewMode: embeddable.getInput().viewMode
154-
? embeddable.getInput().viewMode
155-
: ViewMode.EDIT,
156+
viewMode: embeddable.getInput().viewMode ?? ViewMode.EDIT,
156157
});
157158

158159
this.refreshBadges();
@@ -165,7 +166,9 @@ export class EmbeddablePanel extends React.Component<Props, State> {
165166
this.parentSubscription = parent.getInput$().subscribe(async () => {
166167
if (this.mounted && parent) {
167168
this.setState({
168-
hidePanelTitles: Boolean(parent.getInput().hidePanelTitles),
169+
hidePanelTitle:
170+
Boolean(embeddable.parent?.getInput()?.hidePanelTitles) ||
171+
Boolean(embeddable.getInput()?.hidePanelTitles),
169172
});
170173

171174
this.refreshBadges();
@@ -219,7 +222,7 @@ export class EmbeddablePanel extends React.Component<Props, State> {
219222
{!this.props.hideHeader && (
220223
<PanelHeader
221224
getActionContextMenuPanel={this.getActionContextMenuPanel}
222-
hidePanelTitles={this.state.hidePanelTitles}
225+
hidePanelTitle={this.state.hidePanelTitle}
223226
isViewMode={viewOnlyMode}
224227
closeContextMenu={this.state.closeContextMenu}
225228
title={title}
@@ -272,15 +275,16 @@ export class EmbeddablePanel extends React.Component<Props, State> {
272275

273276
const createGetUserData = (overlays: OverlayStart) =>
274277
async function getUserData(context: { embeddable: IEmbeddable }) {
275-
return new Promise<{ title: string | undefined }>((resolve) => {
278+
return new Promise<{ title: string | undefined; hideTitle?: boolean }>((resolve) => {
276279
const session = overlays.openModal(
277280
toMountPoint(
278281
<CustomizePanelModal
279282
embeddable={context.embeddable}
280-
updateTitle={(title) => {
283+
updateTitle={(title, hideTitle) => {
281284
session.close();
282-
resolve({ title });
285+
resolve({ title, hideTitle });
283286
}}
287+
cancel={() => session.close()}
284288
/>
285289
),
286290
{

src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import { IEmbeddable } from '../../../../embeddables';
2424

2525
export const ACTION_CUSTOMIZE_PANEL = 'ACTION_CUSTOMIZE_PANEL';
2626

27-
type GetUserData = (context: ActionContext) => Promise<{ title: string | undefined }>;
27+
type GetUserData = (
28+
context: ActionContext
29+
) => Promise<{ title: string | undefined; hideTitle?: boolean }>;
2830

2931
interface ActionContext {
3032
embeddable: IEmbeddable;
@@ -52,7 +54,8 @@ export class CustomizePanelTitleAction implements Action<ActionContext> {
5254
}
5355

5456
public async execute({ embeddable }: ActionContext) {
55-
const customTitle = await this.getDataFromUser({ embeddable });
56-
embeddable.updateInput(customTitle);
57+
const data = await this.getDataFromUser({ embeddable });
58+
const { title, hideTitle } = data;
59+
embeddable.updateInput({ title, hidePanelTitles: hideTitle });
5760
}
5861
}

src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,28 @@ import { IEmbeddable } from '../../../../';
3636

3737
interface CustomizePanelProps {
3838
embeddable: IEmbeddable;
39-
updateTitle: (newTitle: string | undefined) => void;
39+
updateTitle: (newTitle: string | undefined, hideTitle: boolean | undefined) => void;
40+
cancel: () => void;
4041
}
4142

4243
interface State {
4344
title: string | undefined;
44-
hideTitle: boolean;
45+
hideTitle: boolean | undefined;
4546
}
4647

4748
export class CustomizePanelModal extends Component<CustomizePanelProps, State> {
4849
constructor(props: CustomizePanelProps) {
4950
super(props);
5051
this.state = {
51-
hideTitle: props.embeddable.getOutput().title === '',
52-
title: props.embeddable.getInput().title,
52+
hideTitle: props.embeddable.getInput().hidePanelTitles,
53+
title: props.embeddable.getInput().title ?? this.props.embeddable.getOutput().defaultTitle,
5354
};
5455
}
5556

56-
private updateTitle = (title: string | undefined) => {
57-
// An empty string will mean "use the default value", which is represented by setting
58-
// title to undefined (where as an empty string is actually used to indicate "hide title").
59-
this.setState({ title: title === '' ? undefined : title });
60-
};
61-
6257
private reset = () => {
63-
this.setState({ title: undefined });
58+
this.setState({
59+
title: this.props.embeddable.getOutput().defaultTitle,
60+
});
6461
};
6562

6663
private onHideTitleToggle = () => {
@@ -70,12 +67,11 @@ export class CustomizePanelModal extends Component<CustomizePanelProps, State> {
7067
};
7168

7269
private save = () => {
73-
if (this.state.hideTitle) {
74-
this.props.updateTitle('');
75-
} else {
76-
const newTitle = this.state.title === '' ? undefined : this.state.title;
77-
this.props.updateTitle(newTitle);
78-
}
70+
const newTitle =
71+
this.state.title === this.props.embeddable.getOutput().defaultTitle
72+
? undefined
73+
: this.state.title;
74+
this.props.updateTitle(newTitle, this.state.hideTitle);
7975
};
8076

8177
public render() {
@@ -116,9 +112,8 @@ export class CustomizePanelModal extends Component<CustomizePanelProps, State> {
116112
name="min"
117113
type="text"
118114
disabled={this.state.hideTitle}
119-
placeholder={this.props.embeddable.getOutput().defaultTitle}
120115
value={this.state.title || ''}
121-
onChange={(e) => this.updateTitle(e.target.value)}
116+
onChange={(e) => this.setState({ title: e.target.value })}
122117
aria-label={i18n.translate(
123118
'embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel',
124119
{
@@ -141,9 +136,7 @@ export class CustomizePanelModal extends Component<CustomizePanelProps, State> {
141136
</EuiFormRow>
142137
</EuiModalBody>
143138
<EuiModalFooter>
144-
<EuiButtonEmpty
145-
onClick={() => this.props.updateTitle(this.props.embeddable.getOutput().title)}
146-
>
139+
<EuiButtonEmpty onClick={() => this.props.cancel()}>
147140
<FormattedMessage
148141
id="embeddableApi.customizePanel.modal.cancel"
149142
defaultMessage="Cancel"

src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ import { uiToReactComponent } from '../../../../../kibana_react/public';
3636
export interface PanelHeaderProps {
3737
title?: string;
3838
isViewMode: boolean;
39-
hidePanelTitles: boolean;
39+
hidePanelTitle: boolean;
4040
getActionContextMenuPanel: () => Promise<EuiContextMenuPanelDescriptor[]>;
4141
closeContextMenu: boolean;
4242
badges: Array<Action<EmbeddableContext>>;
4343
notifications: Array<Action<EmbeddableContext>>;
4444
embeddable: IEmbeddable;
4545
headerId?: string;
46+
showPlaceholderTitle?: boolean;
4647
}
4748

4849
function renderBadges(badges: Array<Action<EmbeddableContext>>, embeddable: IEmbeddable) {
@@ -126,7 +127,7 @@ function getViewDescription(embeddable: IEmbeddable | VisualizeEmbeddable) {
126127
export function PanelHeader({
127128
title,
128129
isViewMode,
129-
hidePanelTitles,
130+
hidePanelTitle,
130131
getActionContextMenuPanel,
131132
closeContextMenu,
132133
badges,
@@ -135,12 +136,30 @@ export function PanelHeader({
135136
headerId,
136137
}: PanelHeaderProps) {
137138
const viewDescription = getViewDescription(embeddable);
138-
const showTitle = !isViewMode || (title && !hidePanelTitles) || viewDescription !== '';
139-
const showPanelBar = badges.length > 0 || showTitle;
139+
const showTitle = !hidePanelTitle && (!isViewMode || title || viewDescription !== '');
140+
const showPanelBar = badges.length > 0 || notifications.length > 0 || showTitle;
140141
const classes = classNames('embPanel__header', {
141142
// eslint-disable-next-line @typescript-eslint/naming-convention
142143
'embPanel__header--floater': !showPanelBar,
143144
});
145+
const placeholderTitle = i18n.translate('embeddableApi.panel.placeholderTitle', {
146+
defaultMessage: '[No Title]',
147+
});
148+
149+
const getAriaLabel = () => {
150+
return (
151+
<span id={headerId}>
152+
{showPanelBar && title
153+
? i18n.translate('embeddableApi.panel.enhancedDashboardPanelAriaLabel', {
154+
defaultMessage: 'Dashboard panel: {title}',
155+
values: { title: title || placeholderTitle },
156+
})
157+
: i18n.translate('embeddableApi.panel.dashboardPanelAriaLabel', {
158+
defaultMessage: 'Dashboard panel',
159+
})}
160+
</span>
161+
);
162+
};
144163

145164
if (!showPanelBar) {
146165
return (
@@ -151,6 +170,7 @@ export function PanelHeader({
151170
closeContextMenu={closeContextMenu}
152171
title={title}
153172
/>
173+
<EuiScreenReaderOnly>{getAriaLabel()}</EuiScreenReaderOnly>
154174
</div>
155175
);
156176
}
@@ -160,34 +180,20 @@ export function PanelHeader({
160180
className={classes}
161181
data-test-subj={`embeddablePanelHeading-${(title || '').replace(/\s/g, '')}`}
162182
>
163-
<h2
164-
id={headerId}
165-
data-test-subj="dashboardPanelTitle"
166-
className="embPanel__title embPanel__dragger"
167-
>
183+
<h2 data-test-subj="dashboardPanelTitle" className="embPanel__title embPanel__dragger">
168184
{showTitle ? (
169185
<span className="embPanel__titleInner">
170-
<span className="embPanel__titleText" aria-hidden="true">
171-
{title}
186+
<span
187+
className={title ? 'embPanel__titleText' : 'embPanel__placeholderTitleText'}
188+
aria-hidden="true"
189+
>
190+
{title || placeholderTitle}
172191
</span>
173-
<EuiScreenReaderOnly>
174-
<span>
175-
{i18n.translate('embeddableApi.panel.enhancedDashboardPanelAriaLabel', {
176-
defaultMessage: 'Dashboard panel: {title}',
177-
values: { title },
178-
})}
179-
</span>
180-
</EuiScreenReaderOnly>
192+
<EuiScreenReaderOnly>{getAriaLabel()}</EuiScreenReaderOnly>
181193
{renderTooltip(viewDescription)}
182194
</span>
183195
) : (
184-
<EuiScreenReaderOnly>
185-
<span>
186-
{i18n.translate('embeddableApi.panel.dashboardPanelAriaLabel', {
187-
defaultMessage: 'Dashboard panel',
188-
})}
189-
</span>
190-
</EuiScreenReaderOnly>
196+
<EuiScreenReaderOnly>{getAriaLabel()}</EuiScreenReaderOnly>
191197
)}
192198
{renderBadges(badges, embeddable)}
193199
</h2>

0 commit comments

Comments
 (0)