Skip to content

Commit

Permalink
Add patch style for oui fixed components (#203) (#205)
Browse files Browse the repository at this point in the history
* add patch style for oui fixed components

Signed-off-by: tygao <tygao@amazon.com>

* add remove child

Signed-off-by: tygao <tygao@amazon.com>

* doc: update changelog

Signed-off-by: tygao <tygao@amazon.com>

* update test mock

Signed-off-by: tygao <tygao@amazon.com>

* add test for hook

Signed-off-by: tygao <tygao@amazon.com>

* remove useRef

Signed-off-by: tygao <tygao@amazon.com>

* use mock requestAnimationFrame to replace delay

Signed-off-by: tygao <tygao@amazon.com>

* add test case for hook unmount

Signed-off-by: tygao <tygao@amazon.com>

* update code structure

Signed-off-by: tygao <tygao@amazon.com>

---------

Signed-off-by: tygao <tygao@amazon.com>
(cherry picked from commit 702f77e)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 7efc32c commit 4277022
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 0 deletions.
3 changes: 3 additions & 0 deletions public/chat_header_button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ jest.spyOn(coreContextExports, 'useCore').mockReturnValue({
element.style.display = 'block';
}
},
getSidecarConfig$: () => {
return new BehaviorSubject(undefined);
},
};
},
},
Expand Down
2 changes: 2 additions & 0 deletions public/chat_header_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from './utils/constants';
import { useCore } from './contexts/core_context';
import { MountPointPortal } from '../../../src/plugins/opensearch_dashboards_react/public';
import { usePatchFixedStyle } from './hooks/use_patch_fixed_style';

interface HeaderChatButtonProps {
application: ApplicationStart;
Expand Down Expand Up @@ -53,6 +54,7 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
const core = useCore();
const flyoutFullScreen = sidecarDockedMode === SIDECAR_DOCKED_MODE.TAKEOVER;
const flyoutMountPoint = useRef(null);
usePatchFixedStyle();

useEffectOnce(() => {
const subscription = props.application.currentAppId$.subscribe((id) => setAppId(id));
Expand Down
175 changes: 175 additions & 0 deletions public/hooks/__snapshots__/use_patch_fixed_style.test.ts.snap

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions public/hooks/use_patch_fixed_style.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { renderHook, act } from '@testing-library/react-hooks';

import { usePatchFixedStyle } from './use_patch_fixed_style';
import * as coreHookExports from '../contexts/core_context';
import { BehaviorSubject } from 'rxjs';
import { ISidecarConfig, SIDECAR_DOCKED_MODE } from '../../../../src/core/public';

describe('usePatchFixedStyle hook', () => {
const sidecarConfig$ = new BehaviorSubject<ISidecarConfig | undefined>({
dockedMode: SIDECAR_DOCKED_MODE.RIGHT,
paddingSize: 300,
});

beforeEach(() => {
jest.spyOn(coreHookExports, 'useCore').mockReturnValue({
overlays: {
// @ts-ignore
sidecar: () => {
return {
getSidecarConfig$: () => {
return sidecarConfig$;
},
};
},
},
});
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
});

afterEach(() => {
jest.clearAllMocks();
window.requestAnimationFrame.mockRestore();
});

it('should patch corresponding left style when sidecarConfig$ pipe', async () => {
renderHook(() => usePatchFixedStyle());
act(() =>
sidecarConfig$.next({
dockedMode: SIDECAR_DOCKED_MODE.LEFT,
paddingSize: 300,
})
);

expect(document.head).toMatchSnapshot();
});

it('should patch corresponding right style when sidecarConfig$ pipe', async () => {
renderHook(() => usePatchFixedStyle());
act(() =>
sidecarConfig$.next({
dockedMode: SIDECAR_DOCKED_MODE.RIGHT,
paddingSize: 300,
})
);

expect(document.head).toMatchSnapshot();
});

it('should patch empty style when isHidden of sidecarConfig$ is true', async () => {
renderHook(() => usePatchFixedStyle());
act(() =>
sidecarConfig$.next({
dockedMode: SIDECAR_DOCKED_MODE.LEFT,
paddingSize: 300,
isHidden: true,
})
);

expect(document.head).toMatchSnapshot();
});

it('should patch empty style when dockedMode of sidecarConfig$ is takeover', async () => {
renderHook(() => usePatchFixedStyle());
act(() =>
sidecarConfig$.next({
dockedMode: SIDECAR_DOCKED_MODE.TAKEOVER,
paddingSize: 300,
})
);

expect(document.head).toMatchSnapshot();
});

it('should not subscribe update after unmount', async () => {
const { unmount } = renderHook(() => usePatchFixedStyle());
act(() =>
sidecarConfig$.next({
dockedMode: SIDECAR_DOCKED_MODE.RIGHT,
paddingSize: 300,
})
);
expect(document.head).toMatchSnapshot();

unmount();

act(() =>
sidecarConfig$.next({
dockedMode: SIDECAR_DOCKED_MODE.LEFT,
paddingSize: 500,
})
);
expect(document.head).toMatchSnapshot();
});
});
90 changes: 90 additions & 0 deletions public/hooks/use_patch_fixed_style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { useEffect } from 'react';

import { useCore } from '../contexts/core_context';
import { ISidecarConfig, SIDECAR_DOCKED_MODE } from '../../../../src/core/public';

// There are some UI components from library whose position is fixed and are not compatible with each other and also not compatible with sidecar container.
// There is currently no way to provide config for these components at runtime.
// This hook patches a style for all these already known components to make them compatible with sidecar.
// TODO: Use config provider from UI library to make this more reasonable.
export const usePatchFixedStyle = () => {
const core = useCore();
const sidecarConfig$ = core.overlays.sidecar().getSidecarConfig$();

useEffect(() => {
const css = '';
const style = document.createElement('style');
const text = document.createTextNode(css);
document.head.appendChild(style);
style.appendChild(text);

const subscription = sidecarConfig$.subscribe((config) => {
if (!config) return;
updateHeadStyle(config, text);
});
return () => {
subscription.unsubscribe();
document.head.removeChild(style);
style.removeChild(text);
};
}, [sidecarConfig$]);
};

function updateHeadStyle(config: ISidecarConfig, text?: Text) {
let css: string;
if (!text) return;

if (
// When sidecar is opened and docked position is left or right, we should patch style.
config?.isHidden !== true &&
config.paddingSize &&
(config.dockedMode === SIDECAR_DOCKED_MODE.LEFT ||
config.dockedMode === SIDECAR_DOCKED_MODE.RIGHT)
) {
const { dockedMode, paddingSize } = config;
if (dockedMode === SIDECAR_DOCKED_MODE.RIGHT) {
// Current applied components include flyout and bottomBar.
// Although the class names of actual rendered component start with eui. We will also apply oui for fallback.
css = `
.euiFlyout:not(.euiFlyout--left) {
right: ${paddingSize}px
}
.ouiFlyout:not(.ouiFlyout--left) {
right: ${paddingSize}px
}
.euiBottomBar{
padding-right: ${paddingSize}px
}
.ouiBottomBar{
padding-right: ${paddingSize}px
}
`;
} else if (dockedMode === SIDECAR_DOCKED_MODE.LEFT) {
css = `
.euiFlyout--left {
left: ${paddingSize}px
}
.ouiFlyout--left {
left: ${paddingSize}px
}
.euiBottomBar{
padding-left: ${paddingSize}px
}
.ouiBottomBar{
padding-left: ${paddingSize}px
}
`;
}
} else {
// If sidecar closes/hides or docked mode changes to takeover, we will set css empty to remove patch style and update.
css = '';
}

requestAnimationFrame(() => {
text.textContent = css;
});
}

0 comments on commit 4277022

Please sign in to comment.