diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index f4fa490e3..787c5ff30 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -43,6 +43,7 @@ test('Correct top level API exposed', () => { "MasterDetail", "NUX", "Panel", + "Spinner", "Tab", "Tabs", "TestUtils", diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index 604f4cd38..0ce137713 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -92,6 +92,7 @@ export {createDataSource} from './state/createDataSource'; export {DataTable, DataTableColumn} from './ui/data-table/DataTable'; export {DataTableManager} from './ui/data-table/DataTableManager'; export {DataList} from './ui/DataList'; +export {Spinner} from './ui/Spinner'; export { Interactive as _Interactive, diff --git a/desktop/flipper-plugin/src/ui/Dialog.tsx b/desktop/flipper-plugin/src/ui/Dialog.tsx index d6fc1907b..31542ca6b 100644 --- a/desktop/flipper-plugin/src/ui/Dialog.tsx +++ b/desktop/flipper-plugin/src/ui/Dialog.tsx @@ -12,6 +12,7 @@ import {Atom, createState, useValue} from '../state/atom'; import React from 'react'; import {renderReactRoot} from '../utils/renderReactRoot'; import {Layout} from './Layout'; +import {Spinner} from './Spinner'; type DialogResult = Promise & {close: () => void}; @@ -19,8 +20,11 @@ type BaseDialogOptions = { title: string; okText?: string; cancelText?: string; + width?: number; }; +const defaultWidth = 400; + export const Dialog = { show( opts: BaseDialogOptions & { @@ -54,7 +58,7 @@ export const Dialog = { } }} onCancel={cancel} - width={400}> + width={opts.width ?? defaultWidth}> {opts.children} @@ -112,6 +116,47 @@ export const Dialog = { }, }); }, + + loading({ + title, + message, + width, + }: { + title?: string; + message: string | React.ReactElement; + width?: number; + }) { + let cancel: () => void; + + return Object.assign( + new Promise((resolve) => { + renderReactRoot((hide) => { + cancel = () => { + hide(); + resolve(); + }; + return ( + + + + {message} + + + ); + }); + }), + { + close() { + cancel(); + }, + }, + ); + }, }; function PromptInput({inputValue}: {inputValue: Atom}) { diff --git a/desktop/flipper-plugin/src/ui/Spinner.tsx b/desktop/flipper-plugin/src/ui/Spinner.tsx new file mode 100644 index 000000000..5c0718071 --- /dev/null +++ b/desktop/flipper-plugin/src/ui/Spinner.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {LoadingOutlined} from '@ant-design/icons'; +import {Spin} from 'antd'; +import React from 'react'; + +export function Spinner({size}: {size?: number}) { + return ( + } /> + ); +} diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 271cdb5dc..bf8c8641d 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -918,10 +918,15 @@ Available utilities * `message`: Text accompanying the input * `defaultValue` * `onConfirm(value) => Promise`. Can be used to transform the inputted value before resolving the prompt promise. If the handler throws, this will be shown as validation error in the dialog. +* `Dialog.loading(options): Promise`. Shows a dialog with a loading spinner. This dialog cannot be closed by the user, so instead `.close()` should be called programmatically on the returned promise. + * `message`: Message to display with the loading spinner. * `Dialog.show(options): Promise Promise`. Handler to handle the OK button, which should produce the value the `Dialog.show` call will resolve to. +### Spinner + +Shows a loading spinner. Accept an optional `size` to make the spinner larger / smaller. ### NUX