Skip to content

OCPBUGS-49709: Add useOverlay hook to dynamic plugin SDK and deprecate useModal hook #14707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@
"Storage Classes": "Storage Classes",
"StorageClasses present in this cluster:": "StorageClasses present in this cluster:",
"Component is resolving": "Component is resolving",
"Test overlay with props": "Test overlay with props",
"Test modal launched with useOverlay": "Test modal launched with useOverlay",
"Overlay modal": "Overlay modal",
"Modal Launchers": "Modal Launchers",
"Launch Modal": "Launch Modal",
"Launch Modal Asynchronously": "Launch Modal Asynchronously",
"Launch overlay": "Launch overlay",
"Launch overlay with props": "Launch overlay with props",
"Launch overlay modal": "Launch overlay modal",
"Hello {{planet}}! I am Thor!": "Hello {{planet}}! I am Thor!",
"Hello {{planet}}! I am Loki!": "Hello {{planet}}! I am Loki!",
"Added title": "Added title",
Expand Down
62 changes: 61 additions & 1 deletion dynamic-demo-plugin/src/components/Modals/ModalPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
K8sResourceCommon,
useK8sWatchResource,
useModal,
useOverlay,
} from '@openshift-console/dynamic-plugin-sdk';
import './modal.scss';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -50,10 +51,39 @@ const LoadingComponent: React.FC = () => {
);
};

const OverlayComponent = ({ closeOverlay, heading = 'Default heading' }) => {
const [right] = React.useState(`${800 * Math.random()}px`);
const [top] = React.useState(`${800 * Math.random()}px`);

return (
<div style={{
backgroundColor: 'gray',
padding: '1rem 4rem',
position: 'absolute',
right,
textAlign: 'center',
top,
zIndex: 999,
}}>
<h2>{heading}</h2>
<Button onClick={closeOverlay}>Close</Button>
</div>
);
};

const OverlayModal = ({ body, closeOverlay, title }) => (
<Modal isOpen onClose={closeOverlay}>
<ModalHeader title={title} />
<ModalBody>{body}</ModalBody>
</Modal>
);

export const TestModalPage: React.FC<{ closeComponent: any }> = () => {
const launchModal = useModal();
const { t } = useTranslation("plugin__console-demo-plugin");

const launchModal = useModal();
const launchOverlay = useOverlay();

const TestComponent = ({ closeModal, ...rest }) => (
<TestModal closeModal={closeModal} {...rest} />
);
Expand All @@ -75,6 +105,27 @@ export const TestModalPage: React.FC<{ closeComponent: any }> = () => {
const onClick = React.useCallback(() => launchModal(TestComponent, {}), [launchModal]);
const onAsyncClick = React.useCallback(() => launchModal(AsyncTestComponent, {}), [launchModal]);

const onClickOverlayBasic = React.useCallback(
() => {
launchOverlay(OverlayComponent, {});
},
[launchOverlay],
);

const onClickOverlayWithProps = React.useCallback(
() => {
launchOverlay(OverlayComponent, { heading: t('Test overlay with props') });
},
[launchOverlay],
);

const onClickOverlayModal = React.useCallback(
() => {
launchOverlay(OverlayModal, { body: t('Test modal launched with useOverlay'), title: t('Overlay modal') });
},
[launchOverlay],
);

return (
<Flex
alignItems={{ default: 'alignItemsCenter' }}
Expand All @@ -88,6 +139,15 @@ export const TestModalPage: React.FC<{ closeComponent: any }> = () => {
<Button onClick={onAsyncClick}>
{t('Launch Modal Asynchronously')}
</Button>
<Button onClick={onClickOverlayBasic}>
{t('plugin__console-demo-plugin~Launch overlay')}
</Button>
<Button onClick={onClickOverlayWithProps}>
{t('plugin__console-demo-plugin~Launch overlay with props')}
</Button>
<Button onClick={onClickOverlayModal}>
{t('plugin__console-demo-plugin~Launch overlay modal')}
</Button>
</Flex>
);
};
68 changes: 60 additions & 8 deletions frontend/packages/console-dynamic-plugin-sdk/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
57. [DocumentTitle](#documenttitle)
58. [usePrometheusPoll](#useprometheuspoll)
59. [Timestamp](#timestamp)
60. [useModal](#usemodal)
60. [useOverlay](#useoverlay)
61. [ActionServiceProvider](#actionserviceprovider)
62. [NamespaceBar](#namespacebar)
63. [ErrorBoundaryFallbackPage](#errorboundaryfallbackpage)
Expand All @@ -74,6 +74,7 @@
72. [DEPRECATED] [useAccessReviewAllowed](#useaccessreviewallowed)
73. [DEPRECATED] [useSafetyFirst](#usesafetyfirst)
74. [DEPRECATED] [YAMLEditor](#yamleditor)
75. [DEPRECATED] [useModal](#usemodal)

---

Expand Down Expand Up @@ -2082,24 +2083,46 @@ A component to render timestamp.<br/>The timestamps are synchronized between ind

---

## `useModal`
## `useOverlay`

### Summary

A hook to launch Modals.
The `useOverlay` hook inserts a component directly to the DOM, outside the web console's page structure. This allows the component to be freely styled and positioning via CSS. For example, to float the overlay in the top right corner of the UI: `style={{ position: 'absolute', right: '2rem', top: '2rem', zIndex: 999 }}`.<br/><br/>It is possible to add multiple overlays by calling `useOverlay` multiple times.<br/><br/>A `closeOverlay` function is passed to the overlay component. Calling it removes the component from the DOM without affecting any other overlays that may have been added via useOverlay.<br/><br/>Additional props can be passed to `useOverlay` and they will be passed through to the overlay component.



### Example


```tsx
const OverlayComponent = ({ closeOverlay, heading }) => {
return (
<div style={{ position: 'absolute', right: '2rem', top: '2rem', zIndex: 999 }}>
<h2>{heading}</h2>
<Button onClick={closeOverlay}>Close</Button>
</div>
);
};

const ModalComponent = ({ body, closeOverlay, title }) => (
<Modal isOpen onClose={closeOverlay}>
<ModalHeader title={title} />
<ModalBody>{body}</ModalBody>
</Modal>
);

const AppPage: React.FC = () => {
const launchModal = useModal();
const onClick = () => launchModal(ModalComponent);
return (
<Button onClick={onClick}>Launch a Modal</Button>
)
const launchOverlay = useOverlay();
const onClickOverlay = () => {
launchOverlay(OverlayComponent, { heading: 'Test overlay' });
};
const onClickModal = () => {
launchOverlay(ModalComponent, { body: 'Test modal', title: 'Overlay modal' });
};
return (
<Button onClick={onClickOverlay}>Launch an Overlay</Button>
<Button onClick={onClickModal}>Launch a Modal</Button>
)
}
```

Expand Down Expand Up @@ -2625,3 +2648,32 @@ An array with a pair of state value and its set function.



---

## `useModal`

### Summary [DEPRECATED]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@opayne1 FYI, we're deprecating a hook from the console plugin API. We should look at release noting this. Let us know what we need to do.


@deprecated - Use useOverlay from \@console/dynamic-plugin-sdk instead.<br/>A hook to launch Modals.



### Example


```tsx
const AppPage: React.FC = () => {
const launchModal = useModal();
const onClick = () => launchModal(ModalComponent);
return (
<Button onClick={onClick}>Launch a Modal</Button>
)
}
```







Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ export const Timestamp: React.FC<TimestampProps> = require('@console/internal/co
.Timestamp;

export { useModal } from '../app/modal-support/useModal';
export { useOverlay } from '../app/modal-support/useOverlay';

/**
* Component that allows to receive contributions from other plugins for the `console.action/provider` extension type.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';

type CloseModal = () => void;
type CloseModalContextValue = () => void;

type UnknownProps = { [key: string]: unknown };
export type ModalComponent<P = UnknownProps> = React.FC<P & { closeModal: CloseModal }>;
Expand All @@ -9,7 +10,7 @@ export type LaunchModal = <P = UnknownProps>(component: ModalComponent<P>, extra

type ModalContextValue = {
launchModal: LaunchModal;
closeModal: CloseModal;
closeModal: CloseModalContextValue;
};

export const ModalContext = React.createContext<ModalContextValue>({
Expand All @@ -30,7 +31,7 @@ export const ModalProvider: React.FC = ({ children }) => {
},
[setOpen, setComponent, setComponentProps],
);
const closeModal = React.useCallback<CloseModal>(() => setOpen(false), [setOpen]);
const closeModal = React.useCallback<CloseModalContextValue>(() => setOpen(false), [setOpen]);

return (
<ModalContext.Provider value={{ launchModal, closeModal }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import * as _ from 'lodash';

type CloseOverlay = () => void;
type CloseOverlayContextValue = (id: string) => void;
type UnknownProps = { [key: string]: unknown };

export type OverlayComponent<P = UnknownProps> = React.FC<P & { closeOverlay: CloseOverlay }>;

export type LaunchOverlay = <P = UnknownProps>(
component: OverlayComponent<P>,
extraProps: P,
) => void;

type OverlayContextValue = {
launchOverlay: LaunchOverlay;
closeOverlay: CloseOverlayContextValue;
};

export const OverlayContext = React.createContext<OverlayContextValue>({
launchOverlay: () => {},
closeOverlay: () => {},
});

type ComponentMap = {
[id: string]: {
Component: OverlayComponent;
props: { [key: string]: any };
};
};

export const OverlayProvider: React.FC = ({ children }) => {
const [componentsMap, setComponentsMap] = React.useState<ComponentMap>({});

const launchOverlay = React.useCallback<LaunchOverlay>((component, componentProps) => {
const id = _.uniqueId('plugin-overlay-');
setComponentsMap((components) => ({
...components,
[id]: { Component: component, props: componentProps },
}));
}, []);

const closeOverlay = React.useCallback<CloseOverlayContextValue>((id) => {
setComponentsMap((components) => _.omit(components, id));
}, []);

return (
<OverlayContext.Provider value={{ launchOverlay, closeOverlay }}>
{_.map(componentsMap, (c, id) => (
<c.Component {...c.props} key={id} closeOverlay={() => closeOverlay(id)} />
))}
{children}
</OverlayContext.Provider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LaunchModal, ModalContext } from './ModalProvider';
type UseModalLauncher = () => LaunchModal;

/**
* @deprecated - Use useOverlay from \@console/dynamic-plugin-sdk instead.
* A hook to launch Modals.
* @example
*```tsx
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react';
import { LaunchOverlay, OverlayContext } from './OverlayProvider';

type UseOverlayLauncher = () => LaunchOverlay;

/**
* The `useOverlay` hook inserts a component directly to the DOM, outside the web console's page structure. This allows the component to be freely styled and positioning via CSS. For example, to float the overlay in the top right corner of the UI: `style={{ position: 'absolute', right: '2rem', top: '2rem', zIndex: 999 }}`.
*
* It is possible to add multiple overlays by calling `useOverlay` multiple times.
*
* A `closeOverlay` function is passed to the overlay component. Calling it removes the component from the DOM without affecting any other overlays that may have been added via useOverlay.
*
* Additional props can be passed to `useOverlay` and they will be passed through to the overlay component.
* @example
*```tsx
* const OverlayComponent = ({ closeOverlay, heading }) => {
* return (
* <div style={{ position: 'absolute', right: '2rem', top: '2rem', zIndex: 999 }}>
* <h2>{heading}</h2>
* <Button onClick={closeOverlay}>Close</Button>
* </div>
* );
* };
*
* const ModalComponent = ({ body, closeOverlay, title }) => (
* <Modal isOpen onClose={closeOverlay}>
* <ModalHeader title={title} />
* <ModalBody>{body}</ModalBody>
* </Modal>
* );
*
* const AppPage: React.FC = () => {
* const launchOverlay = useOverlay();
* const onClickOverlay = () => {
* launchOverlay(OverlayComponent, { heading: 'Test overlay' });
* };
* const onClickModal = () => {
* launchOverlay(ModalComponent, { body: 'Test modal', title: 'Overlay modal' });
* };
* return (
* <Button onClick={onClickOverlay}>Launch an Overlay</Button>
* <Button onClick={onClickModal}>Launch a Modal</Button>
* )
* }
* ```
*/
export const useOverlay: UseOverlayLauncher = () => {
const { launchOverlay } = React.useContext(OverlayContext);
return launchOverlay;
};
19 changes: 11 additions & 8 deletions frontend/public/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { initConsolePlugins } from '@console/dynamic-plugin-sdk/src/runtime/plug
import { GuidedTour } from '@console/app/src/components/tour';
import QuickStartDrawer from '@console/app/src/components/quick-starts/QuickStartDrawerAsync';
import { ModalProvider } from '@console/dynamic-plugin-sdk/src/app/modal-support/ModalProvider';
import { OverlayProvider } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import { settleAllPromises } from '@console/dynamic-plugin-sdk/src/utils/promise';
import ToastProvider from '@console/shared/src/components/toast/ToastProvider';
import { useToast } from '@console/shared/src/components/toast';
Expand Down Expand Up @@ -275,14 +276,16 @@ const App = (props) => {
<CaptureTelemetry />
<DetectNamespace>
<ModalProvider>
{contextProviderExtensions.reduce(
(children, e) => (
<EnhancedProvider key={e.uid} {...e.properties}>
{children}
</EnhancedProvider>
),
content,
)}
<OverlayProvider>
{contextProviderExtensions.reduce(
(children, e) => (
<EnhancedProvider key={e.uid} {...e.properties}>
{children}
</EnhancedProvider>
),
content,
)}
</OverlayProvider>
</ModalProvider>
</DetectNamespace>
<DetectLanguage />
Expand Down