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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
419497db7e
commit
846246ffae
@@ -7,8 +7,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Alert, Input, Modal, Typography} from 'antd';
|
import {Alert, Input, Modal, Radio, Space, Typography} from 'antd';
|
||||||
import {Atom, createState, useValue} from '../state/atom';
|
import {createState, useValue} from '../state/atom';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {renderReactRoot} from '../utils/renderReactRoot';
|
import {renderReactRoot} from '../utils/renderReactRoot';
|
||||||
import {Layout} from './Layout';
|
import {Layout} from './Layout';
|
||||||
@@ -28,20 +28,31 @@ const defaultWidth = 400;
|
|||||||
export const Dialog = {
|
export const Dialog = {
|
||||||
show<T>(
|
show<T>(
|
||||||
opts: BaseDialogOptions & {
|
opts: BaseDialogOptions & {
|
||||||
children: React.ReactNode;
|
defaultValue: T;
|
||||||
onConfirm: () => Promise<T>;
|
children: (currentValue: T, setValue: (v: T) => void) => React.ReactNode;
|
||||||
|
onConfirm?: (currentValue: T) => Promise<T>;
|
||||||
|
onValidate?: (value: T) => string;
|
||||||
},
|
},
|
||||||
): DialogResult<T> {
|
): DialogResult<T> {
|
||||||
let cancel: () => void;
|
let cancel: () => void;
|
||||||
|
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
new Promise<false | T>((resolve) => {
|
new Promise<false | T>((resolve) => {
|
||||||
renderReactRoot((hide) => {
|
const state = createState<T>(opts.defaultValue);
|
||||||
const submissionError = createState<string>('');
|
const submissionError = createState<string>('');
|
||||||
cancel = () => {
|
|
||||||
hide();
|
// create inline component to subscribe to dialog state
|
||||||
resolve(false);
|
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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={opts.title}
|
title={opts.title}
|
||||||
@@ -50,21 +61,36 @@ export const Dialog = {
|
|||||||
cancelText={opts.cancelText}
|
cancelText={opts.cancelText}
|
||||||
onOk={async () => {
|
onOk={async () => {
|
||||||
try {
|
try {
|
||||||
const value = await opts.onConfirm();
|
const value = opts.onConfirm
|
||||||
hide();
|
? await opts.onConfirm(currentValue)
|
||||||
|
: currentValue!;
|
||||||
|
onHide();
|
||||||
resolve(value);
|
resolve(value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
submissionError.set(e.toString());
|
submissionError.set(e.toString());
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
okButtonProps={{
|
||||||
|
disabled: opts.onValidate
|
||||||
|
? opts.onValidate(currentValue) !== ''
|
||||||
|
: false,
|
||||||
|
}}
|
||||||
onCancel={cancel}
|
onCancel={cancel}
|
||||||
width={opts.width ?? defaultWidth}>
|
width={opts.width ?? defaultWidth}>
|
||||||
<Layout.Container gap>
|
<Layout.Container gap>
|
||||||
{opts.children}
|
{opts.children(currentValue, setCurrentValue)}
|
||||||
<SubmissionError submissionError={submissionError} />
|
{currentError && <Alert type="error" message={currentError} />}
|
||||||
</Layout.Container>
|
</Layout.Container>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderReactRoot((hide) => {
|
||||||
|
cancel = () => {
|
||||||
|
hide();
|
||||||
|
resolve(false);
|
||||||
|
};
|
||||||
|
return <DialogComponent onHide={hide} />;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@@ -85,8 +111,9 @@ export const Dialog = {
|
|||||||
} & BaseDialogOptions): DialogResult<true> {
|
} & BaseDialogOptions): DialogResult<true> {
|
||||||
return Dialog.show<true>({
|
return Dialog.show<true>({
|
||||||
...rest,
|
...rest,
|
||||||
children: message,
|
defaultValue: true,
|
||||||
onConfirm: onConfirm ?? (async () => true),
|
children: () => message,
|
||||||
|
onConfirm: onConfirm,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -97,14 +124,22 @@ export const Dialog = {
|
|||||||
}: {
|
}: {
|
||||||
message: React.ReactNode;
|
message: React.ReactNode;
|
||||||
type: 'info' | 'error' | 'warning' | 'success';
|
type: 'info' | 'error' | 'warning' | 'success';
|
||||||
} & BaseDialogOptions): Promise<void> {
|
} & BaseDialogOptions): Promise<void> & {close(): void} {
|
||||||
return new Promise((resolve) => {
|
let modalRef: ReturnType<typeof Modal['info']>;
|
||||||
Modal[type]({
|
return Object.assign(
|
||||||
afterClose: resolve,
|
new Promise<void>((resolve) => {
|
||||||
content: message,
|
modalRef = Modal[type]({
|
||||||
...rest,
|
afterClose: resolve,
|
||||||
});
|
content: message,
|
||||||
});
|
...rest,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
close() {
|
||||||
|
modalRef.destroy();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
prompt({
|
prompt({
|
||||||
@@ -117,22 +152,53 @@ export const Dialog = {
|
|||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
onConfirm?: (value: string) => Promise<string>;
|
onConfirm?: (value: string) => Promise<string>;
|
||||||
}): DialogResult<string> {
|
}): DialogResult<string> {
|
||||||
const inputValue = createState(defaultValue ?? '');
|
|
||||||
return Dialog.show<string>({
|
return Dialog.show<string>({
|
||||||
...rest,
|
...rest,
|
||||||
children: (
|
defaultValue: defaultValue ?? '',
|
||||||
<>
|
children: (value, onChange) => (
|
||||||
|
<Layout.Container gap>
|
||||||
<Typography.Text>{message}</Typography.Text>
|
<Typography.Text>{message}</Typography.Text>
|
||||||
<PromptInput inputValue={inputValue} />
|
<Input value={value} onChange={(e) => onChange(e.target.value)} />
|
||||||
</>
|
</Layout.Container>
|
||||||
),
|
),
|
||||||
onConfirm: async () => {
|
onValidate: (value) => (value ? '' : 'No input provided'),
|
||||||
const value = inputValue.get();
|
onConfirm,
|
||||||
if (onConfirm) {
|
});
|
||||||
return await onConfirm(value);
|
},
|
||||||
}
|
|
||||||
return value;
|
options({
|
||||||
},
|
message,
|
||||||
|
onConfirm,
|
||||||
|
options,
|
||||||
|
...rest
|
||||||
|
}: BaseDialogOptions & {
|
||||||
|
message: React.ReactNode;
|
||||||
|
options: {label: string; value: string}[];
|
||||||
|
onConfirm?: (value: string) => Promise<string>;
|
||||||
|
}): DialogResult<string> {
|
||||||
|
return Dialog.show<string>({
|
||||||
|
...rest,
|
||||||
|
defaultValue: '',
|
||||||
|
onValidate: (value) => (value === '' ? 'Please select an option' : ''),
|
||||||
|
children: (value, onChange) => (
|
||||||
|
<Layout.Container gap style={{maxHeight: '50vh', overflow: 'auto'}}>
|
||||||
|
<Typography.Text>{message}</Typography.Text>
|
||||||
|
<Radio.Group
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange(e.target.value);
|
||||||
|
}}>
|
||||||
|
<Space direction="vertical">
|
||||||
|
{options.map((o) => (
|
||||||
|
<Radio value={o.value} key={o.value}>
|
||||||
|
{o.label}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Radio.Group>
|
||||||
|
</Layout.Container>
|
||||||
|
),
|
||||||
|
onConfirm,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -177,20 +243,3 @@ export const Dialog = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function PromptInput({inputValue}: {inputValue: Atom<string>}) {
|
|
||||||
const currentValue = useValue(inputValue);
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
value={currentValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
inputValue.set(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SubmissionError({submissionError}: {submissionError: Atom<string>}) {
|
|
||||||
const currentError = useValue(submissionError);
|
|
||||||
return currentError ? <Alert type="error" message={currentError} /> : null;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user