Skip to content

Commit a013995

Browse files
Fix bottom bar visibility using create portal (opensearch-project#3336)
Co-authored-by: Andrey Myssak <andreymyssak@gmail.com> Signed-off-by: Sergey Myssak <sergey.myssak@gmail.com>
1 parent 6e352ff commit a013995

File tree

7 files changed

+79
-66
lines changed

7 files changed

+79
-66
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
134134
- [Table Visualization] Fix table rendering empty unused space ([#3797](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3797))
135135
- [Table Visualization] Fix data table not adjusting height on the initial load ([#3816](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3816))
136136
- Cleanup unused url ([#3847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3847))
137+
- [BUG] Docked navigation impacts visibility of bottom bar component ([#3978](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3978))
137138

138139
### 🚞 Infrastructure
139140

src/core/public/rendering/_base.scss

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
// SASSTODO: Naming here is too embedded and high up that changing them could cause major breaks
77
#opensearch-dashboards-body {
8-
overflow-x: hidden;
98
min-height: 100%;
109
}
1110

src/core/public/rendering/app_containers.test.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('AppWrapper', () => {
4343
expect(component.getDOMNode()).toMatchInlineSnapshot(`
4444
<div
4545
class="app-wrapper"
46+
id="app-wrapper"
4647
>
4748
app-content
4849
</div>
@@ -53,6 +54,7 @@ describe('AppWrapper', () => {
5354
expect(component.getDOMNode()).toMatchInlineSnapshot(`
5455
<div
5556
class="app-wrapper hidden-chrome"
57+
id="app-wrapper"
5658
>
5759
app-content
5860
</div>
@@ -63,6 +65,7 @@ describe('AppWrapper', () => {
6365
expect(component.getDOMNode()).toMatchInlineSnapshot(`
6466
<div
6567
class="app-wrapper"
68+
id="app-wrapper"
6669
>
6770
app-content
6871
</div>

src/core/public/rendering/app_containers.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ export const AppWrapper: React.FunctionComponent<{
3737
chromeVisible$: Observable<boolean>;
3838
}> = ({ chromeVisible$, children }) => {
3939
const visible = useObservable(chromeVisible$);
40-
return <div className={classNames('app-wrapper', { 'hidden-chrome': !visible })}>{children}</div>;
40+
return (
41+
<div id="app-wrapper" className={classNames('app-wrapper', { 'hidden-chrome': !visible })}>
42+
{children}
43+
</div>
44+
);
4145
};
4246

4347
export const AppContainer: React.FunctionComponent<{

src/plugins/advanced_settings/public/management_app/_advanced_settings.scss

-4
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,3 @@
7676
.mgtAdvancedSettingsForm__button {
7777
width: 100%;
7878
}
79-
80-
.osdBody--mgtAdvancedSettingsHasBottomBar .mgtPage__body {
81-
padding-bottom: $euiSizeXL * 2;
82-
}

src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
*/
3030

3131
import React from 'react';
32+
import ReactDOM from 'react-dom';
3233
import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers';
3334
import { UiSettingsType } from '../../../../../../core/public';
3435

@@ -38,13 +39,20 @@ import { notificationServiceMock } from '../../../../../../core/public/mocks';
3839
import { SettingsChanges } from '../../types';
3940
import { Form } from './form';
4041

42+
jest.mock('react-dom', () => ({
43+
...jest.requireActual('react-dom'),
44+
createPortal: jest.fn((element) => element),
45+
}));
46+
4147
jest.mock('../field', () => ({
4248
Field: () => {
4349
return 'field';
4450
},
4551
}));
4652

4753
beforeAll(() => {
54+
ReactDOM.createPortal = jest.fn((children: any) => children);
55+
4856
const localStorage: Record<string, any> = {
4957
'core.chrome.isLocked': true,
5058
};
@@ -60,6 +68,7 @@ beforeAll(() => {
6068
});
6169

6270
afterAll(() => {
71+
(ReactDOM.createPortal as jest.Mock).mockClear();
6372
delete (window as any).localStorage;
6473
});
6574

src/plugins/advanced_settings/public/management_app/components/form/form.tsx

+61-60
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ import {
4747
import { FormattedMessage } from '@osd/i18n/react';
4848
import { isEmpty } from 'lodash';
4949
import { i18n } from '@osd/i18n';
50+
import { DocLinksStart, ToastsStart } from 'opensearch-dashboards/public';
51+
import { createPortal } from 'react-dom';
5052
import { toMountPoint } from '../../../../../opensearch_dashboards_react/public';
51-
import { DocLinksStart, ToastsStart } from '../../../../../../core/public';
5253

5354
import { getCategoryName } from '../../lib';
5455
import { Field, getEditableValue } from '../field';
@@ -336,63 +337,69 @@ export class Form extends PureComponent<FormProps> {
336337
};
337338

338339
renderBottomBar = () => {
339-
const areChangesInvalid = this.areChangesInvalid();
340-
return (
341-
<EuiBottomBar data-test-subj="advancedSetting-bottomBar">
342-
<EuiFlexGroup
343-
justifyContent="spaceBetween"
344-
alignItems="center"
345-
responsive={false}
346-
gutterSize="s"
347-
>
348-
<EuiFlexItem grow={false} className="mgtAdvancedSettingsForm__unsavedCount">
349-
<p id="aria-describedby.countOfUnsavedSettings">{this.renderCountOfUnsaved()}</p>
350-
</EuiFlexItem>
351-
<EuiFlexItem />
352-
<EuiFlexItem grow={false}>
353-
<EuiButtonEmpty
354-
color="ghost"
355-
size="s"
356-
iconType="cross"
357-
onClick={this.clearAllUnsaved}
358-
aria-describedby="aria-describedby.countOfUnsavedSettings"
359-
data-test-subj="advancedSetting-cancelButton"
360-
>
361-
{i18n.translate('advancedSettings.form.cancelButtonLabel', {
362-
defaultMessage: 'Cancel changes',
363-
})}
364-
</EuiButtonEmpty>
365-
</EuiFlexItem>
366-
<EuiFlexItem grow={false}>
367-
<EuiToolTip
368-
content={
369-
areChangesInvalid &&
370-
i18n.translate('advancedSettings.form.saveButtonTooltipWithInvalidChanges', {
371-
defaultMessage: 'Fix invalid settings before saving.',
372-
})
373-
}
374-
>
375-
<EuiButton
376-
className="mgtAdvancedSettingsForm__button"
377-
disabled={areChangesInvalid}
378-
color="secondary"
379-
fill
340+
try {
341+
const areChangesInvalid = this.areChangesInvalid();
342+
const bottomBar = (
343+
<EuiBottomBar data-test-subj="advancedSetting-bottomBar" position="sticky">
344+
<EuiFlexGroup
345+
justifyContent="spaceBetween"
346+
alignItems="center"
347+
responsive={false}
348+
gutterSize="s"
349+
>
350+
<EuiFlexItem grow={false} className="mgtAdvancedSettingsForm__unsavedCount">
351+
<p id="aria-describedby.countOfUnsavedSettings">{this.renderCountOfUnsaved()}</p>
352+
</EuiFlexItem>
353+
<EuiFlexItem />
354+
<EuiFlexItem grow={false}>
355+
<EuiButtonEmpty
356+
color="ghost"
380357
size="s"
381-
iconType="check"
382-
onClick={this.saveAll}
358+
iconType="cross"
359+
onClick={this.clearAllUnsaved}
383360
aria-describedby="aria-describedby.countOfUnsavedSettings"
384-
isLoading={this.state.loading}
385-
data-test-subj="advancedSetting-saveButton"
361+
data-test-subj="advancedSetting-cancelButton"
386362
>
387-
{i18n.translate('advancedSettings.form.saveButtonLabel', {
388-
defaultMessage: 'Save changes',
363+
{i18n.translate('advancedSettings.form.cancelButtonLabel', {
364+
defaultMessage: 'Cancel changes',
389365
})}
390-
</EuiButton>
391-
</EuiToolTip>
392-
</EuiFlexItem>
393-
</EuiFlexGroup>
394-
</EuiBottomBar>
395-
);
366+
</EuiButtonEmpty>
367+
</EuiFlexItem>
368+
<EuiFlexItem grow={false}>
369+
<EuiToolTip
370+
content={
371+
areChangesInvalid &&
372+
i18n.translate('advancedSettings.form.saveButtonTooltipWithInvalidChanges', {
373+
defaultMessage: 'Fix invalid settings before saving.',
374+
})
375+
}
376+
>
377+
<EuiButton
378+
className="mgtAdvancedSettingsForm__button"
379+
disabled={areChangesInvalid}
380+
color="secondary"
381+
fill
382+
size="s"
383+
iconType="check"
384+
onClick={this.saveAll}
385+
aria-describedby="aria-describedby.countOfUnsavedSettings"
386+
isLoading={this.state.loading}
387+
data-test-subj="advancedSetting-saveButton"
388+
>
389+
{i18n.translate('advancedSettings.form.saveButtonLabel', {
390+
defaultMessage: 'Save changes',
391+
})}
392+
</EuiButton>
393+
</EuiToolTip>
394+
</EuiFlexItem>
395+
</EuiFlexGroup>
396+
</EuiBottomBar>
397+
);
398+
399+
return createPortal(bottomBar, document.getElementById('app-wrapper')!);
400+
} catch (e) {
401+
return null;
402+
}
396403
};
397404

398405
render() {
@@ -401,12 +408,6 @@ export class Form extends PureComponent<FormProps> {
401408
const currentCategories: Category[] = [];
402409
const hasUnsavedChanges = !isEmpty(unsavedChanges);
403410

404-
if (hasUnsavedChanges) {
405-
document.body.classList.add('osdBody--mgtAdvancedSettingsHasBottomBar');
406-
} else {
407-
document.body.classList.remove('osdBody--mgtAdvancedSettingsHasBottomBar');
408-
}
409-
410411
categories.forEach((category) => {
411412
if (visibleSettings[category] && visibleSettings[category].length) {
412413
currentCategories.push(category);

0 commit comments

Comments
 (0)