diff --git a/.changeset/cuddly-apricots-fetch.md b/.changeset/cuddly-apricots-fetch.md new file mode 100644 index 0000000000..77126268a8 --- /dev/null +++ b/.changeset/cuddly-apricots-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added a new Utility API, `DialogApi`, which can be used to show dialogs in the React tree that can collect input from the user. diff --git a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts new file mode 100644 index 0000000000..ce19cb8d26 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts @@ -0,0 +1,178 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from '@backstage/core-plugin-api'; + +/** + * A handle for an open dialog that can be used to interact with it. + * + * @remarks + * + * Dialogs can be opened using either {@link DialogApi.show} or {@link DialogApi.showModal}. + * + * @public + */ +export interface DialogApiDialog { + /** + * Closes the dialog with thet provided result. + * + * @remarks + * + * If the dialog is a modal dialog a result must always be provided. If it's a regular dialog then passing a result is optional. + */ + close( + ...args: undefined extends TResult ? [result?: TResult] : [result: TResult] + ): void; + + /** + * Replaces the content of the dialog with the provided element or component, causing it to be rerenedered. + */ + update( + elementOrComponent: + | React.JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element), + ): void; + + /** + * Wait until the dialog is closed and return the result. + * + * @remarks + * + * If the dialog is a modal dialog a result will always be returned. If it's a regular dialog then the result may be `undefined`. + */ + result(): Promise; +} + +/** + * A Utility API for showing dialogs that render in the React tree and return a result. + * + * @public + */ +export interface DialogApi { + /** + * Opens a modal dialog and returns a handle to it. + * + * @remarks + * + * This dialog can be closed by calling the `close` method on the returned handle, optionally providing a result. + * The dialog can also be closed by the user by clicking the backdrop or pressing the escape key. + * + * If the dialog is closed without a result, the result will be `undefined`. + * + * @example + * + * ### Example with inline dialog content + * ```tsx + * const dialog = dialogApi.show( + * + * Are you sure? + * + * + * + * + * + * ); + * const result = await dialog.result(); + * ``` + * + * @example + * + * ### Example with separate dialog component + * ```tsx + * function CustomDialog({ dialog }: { dialog: DialogApiDialog }) { + * return ( + * + * Are you sure? + * + * + * + * + * + * ) + * } + * const result = await dialogApi.show(CustomDialog).result(); + * ``` + * + * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. + * @public + */ + show( + elementOrComponent: + | JSX.Element + | ((props: { + dialog: DialogApiDialog; + }) => JSX.Element), + ): DialogApiDialog; + + /** + * Opens a modal dialog and returns a handle to it. + * + * @remarks + * + * This dialog can not be closed in any other way than calling the `close` method on the returned handle and providing a result. + * + * @example + * + * ### Example with inline dialog content + * ```tsx + * const dialog = dialogApi.showModal( + * + * Are you sure? + * + * + * + * + * + * ); + * const result = await dialog.result(); + * ``` + * + * @example + * + * ### Example with separate dialog component + * ```tsx + * function CustomDialog({ dialog }: { dialog: DialogApiDialog }) { + * return ( + * + * Are you sure? + * + * + * + * + * + * ) + * } + * const result = await dialogApi.showModal(CustomDialog).result(); + * ``` + * + * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. + * @public + */ + showModal( + elementOrComponent: + | JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element), + ): DialogApiDialog; +} + +/** + * The `ApiRef` of {@link DialogApi}. + * + * @public + */ +export const dialogApiRef = createApiRef({ + id: 'core.dialog', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index d766a8dfdf..0da23fcacb 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -42,6 +42,7 @@ export * from './FeatureFlagsApi'; export * from './FetchApi'; export * from './IconsApi'; export * from './IdentityApi'; +export * from './DialogApi'; export * from './OAuthRequestApi'; export * from './RouteResolutionApi'; export * from './StorageApi';