From 846246ffaef4e70f9cda1ae846c66a2b89d93ccb Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Fri, 20 Aug 2021 02:23:05 -0700 Subject: [PATCH] Introduce options dialog, cleaned up state managament Summary: Made it easier to build 'pick' dialogs, and introduced Dialogs.options for a set of radio buttions Reviewed By: timur-valiev Differential Revision: D30424708 fbshipit-source-id: 98abd0d64f47c552c81053b4433e5fc524574145 --- desktop/flipper-plugin/src/ui/Dialog.tsx | 153 +++++++++++++++-------- 1 file changed, 101 insertions(+), 52 deletions(-) diff --git a/desktop/flipper-plugin/src/ui/Dialog.tsx b/desktop/flipper-plugin/src/ui/Dialog.tsx index 679de22da..4c9f4c32d 100644 --- a/desktop/flipper-plugin/src/ui/Dialog.tsx +++ b/desktop/flipper-plugin/src/ui/Dialog.tsx @@ -7,8 +7,8 @@ * @format */ -import {Alert, Input, Modal, Typography} from 'antd'; -import {Atom, createState, useValue} from '../state/atom'; +import {Alert, Input, Modal, Radio, Space, Typography} from 'antd'; +import {createState, useValue} from '../state/atom'; import React from 'react'; import {renderReactRoot} from '../utils/renderReactRoot'; import {Layout} from './Layout'; @@ -28,20 +28,31 @@ const defaultWidth = 400; export const Dialog = { show( opts: BaseDialogOptions & { - children: React.ReactNode; - onConfirm: () => Promise; + defaultValue: T; + children: (currentValue: T, setValue: (v: T) => void) => React.ReactNode; + onConfirm?: (currentValue: T) => Promise; + onValidate?: (value: T) => string; }, ): DialogResult { let cancel: () => void; return Object.assign( new Promise((resolve) => { - renderReactRoot((hide) => { - const submissionError = createState(''); - cancel = () => { - hide(); - resolve(false); + const state = createState(opts.defaultValue); + const submissionError = createState(''); + + // create inline component to subscribe to dialog state + const DialogComponent = ({onHide}: {onHide: () => void}) => { + const currentValue = useValue(state); + const currentError = useValue(submissionError); + + const setCurrentValue = (v: T) => { + state.set(v); + if (opts.onValidate) { + submissionError.set(opts.onValidate(v)); + } }; + return ( { try { - const value = await opts.onConfirm(); - hide(); + const value = opts.onConfirm + ? await opts.onConfirm(currentValue) + : currentValue!; + onHide(); resolve(value); } catch (e) { submissionError.set(e.toString()); } }} + okButtonProps={{ + disabled: opts.onValidate + ? opts.onValidate(currentValue) !== '' + : false, + }} onCancel={cancel} width={opts.width ?? defaultWidth}> - {opts.children} - + {opts.children(currentValue, setCurrentValue)} + {currentError && } ); + }; + + renderReactRoot((hide) => { + cancel = () => { + hide(); + resolve(false); + }; + return ; }); }), { @@ -85,8 +111,9 @@ export const Dialog = { } & BaseDialogOptions): DialogResult { return Dialog.show({ ...rest, - children: message, - onConfirm: onConfirm ?? (async () => true), + defaultValue: true, + children: () => message, + onConfirm: onConfirm, }); }, @@ -97,14 +124,22 @@ export const Dialog = { }: { message: React.ReactNode; type: 'info' | 'error' | 'warning' | 'success'; - } & BaseDialogOptions): Promise { - return new Promise((resolve) => { - Modal[type]({ - afterClose: resolve, - content: message, - ...rest, - }); - }); + } & BaseDialogOptions): Promise & {close(): void} { + let modalRef: ReturnType; + return Object.assign( + new Promise((resolve) => { + modalRef = Modal[type]({ + afterClose: resolve, + content: message, + ...rest, + }); + }), + { + close() { + modalRef.destroy(); + }, + }, + ); }, prompt({ @@ -117,22 +152,53 @@ export const Dialog = { defaultValue?: string; onConfirm?: (value: string) => Promise; }): DialogResult { - const inputValue = createState(defaultValue ?? ''); return Dialog.show({ ...rest, - children: ( - <> + defaultValue: defaultValue ?? '', + children: (value, onChange) => ( + {message} - - + onChange(e.target.value)} /> + ), - onConfirm: async () => { - const value = inputValue.get(); - if (onConfirm) { - return await onConfirm(value); - } - return value; - }, + onValidate: (value) => (value ? '' : 'No input provided'), + onConfirm, + }); + }, + + options({ + message, + onConfirm, + options, + ...rest + }: BaseDialogOptions & { + message: React.ReactNode; + options: {label: string; value: string}[]; + onConfirm?: (value: string) => Promise; + }): DialogResult { + return Dialog.show({ + ...rest, + defaultValue: '', + onValidate: (value) => (value === '' ? 'Please select an option' : ''), + children: (value, onChange) => ( + + {message} + { + onChange(e.target.value); + }}> + + {options.map((o) => ( + + {o.label} + + ))} + + + + ), + onConfirm, }); }, @@ -177,20 +243,3 @@ export const Dialog = { ); }, }; - -function PromptInput({inputValue}: {inputValue: Atom}) { - const currentValue = useValue(inputValue); - return ( - { - inputValue.set(e.target.value); - }} - /> - ); -} - -function SubmissionError({submissionError}: {submissionError: Atom}) { - const currentError = useValue(submissionError); - return currentError ? : null; -}