mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ConfirmModal: Reuse confirm content (#88577)
This commit is contained in:
parent
b30c81b1ad
commit
03a000e1b5
@ -0,0 +1,140 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Box } from '../Layout/Box/Box';
|
||||
import { Stack } from '../Layout/Stack/Stack';
|
||||
import { JustifyContent } from '../Layout/types';
|
||||
import { ResponsiveProp } from '../Layout/utils/responsiveness';
|
||||
|
||||
export interface ConfirmContentProps {
|
||||
/** Modal content */
|
||||
body: React.ReactNode;
|
||||
/** Modal description */
|
||||
description?: React.ReactNode;
|
||||
/** Text for confirm button */
|
||||
confirmButtonLabel: string;
|
||||
/** Confirm button variant */
|
||||
confirmButtonVariant?: ButtonVariant;
|
||||
/** Text user needs to fill in before confirming */
|
||||
confirmPromptText?: string;
|
||||
/** Text for dismiss button */
|
||||
dismissButtonLabel?: string;
|
||||
/** Variant for dismiss button */
|
||||
dismissButtonVariant?: ButtonVariant;
|
||||
/** Text for alternative button */
|
||||
alternativeButtonLabel?: string;
|
||||
/** Justify for buttons placement */
|
||||
justifyButtons?: ResponsiveProp<JustifyContent>;
|
||||
/** Confirm action callback
|
||||
* Return a promise to disable the confirm button until the promise is resolved
|
||||
*/
|
||||
onConfirm(): void | Promise<void>;
|
||||
/** Dismiss action callback */
|
||||
onDismiss(): void;
|
||||
/** Alternative action callback */
|
||||
onAlternative?(): void;
|
||||
}
|
||||
|
||||
export const ConfirmContent = ({
|
||||
body,
|
||||
confirmPromptText,
|
||||
confirmButtonLabel,
|
||||
confirmButtonVariant,
|
||||
dismissButtonVariant,
|
||||
dismissButtonLabel,
|
||||
onConfirm,
|
||||
onDismiss,
|
||||
onAlternative,
|
||||
alternativeButtonLabel,
|
||||
description,
|
||||
justifyButtons = 'flex-end',
|
||||
}: ConfirmContentProps) => {
|
||||
const [disabled, setDisabled] = useState(Boolean(confirmPromptText));
|
||||
const styles = useStyles2(getStyles);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const onConfirmationTextChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
setDisabled(confirmPromptText?.toLowerCase().localeCompare(event.currentTarget.value.toLowerCase()) !== 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
buttonRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setDisabled(Boolean(confirmPromptText));
|
||||
}, [confirmPromptText]);
|
||||
|
||||
const onConfirmClick = async () => {
|
||||
setDisabled(true);
|
||||
try {
|
||||
await onConfirm();
|
||||
} finally {
|
||||
setDisabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { handleSubmit } = useForm();
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onConfirmClick)}>
|
||||
<div className={styles.text}>
|
||||
{body}
|
||||
{description ? <div className={styles.description}>{description}</div> : null}
|
||||
{confirmPromptText ? (
|
||||
<div className={styles.confirmationInput}>
|
||||
<Stack alignItems="flex-start">
|
||||
<Box>
|
||||
<Input placeholder={`Type "${confirmPromptText}" to confirm`} onChange={onConfirmationTextChange} />
|
||||
</Box>
|
||||
</Stack>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Stack justifyContent={justifyButtons} gap={2} wrap="wrap">
|
||||
<Button variant={dismissButtonVariant} onClick={onDismiss} fill="outline">
|
||||
{dismissButtonLabel}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant={confirmButtonVariant}
|
||||
disabled={disabled}
|
||||
ref={buttonRef}
|
||||
data-testid={selectors.pages.ConfirmModal.delete}
|
||||
>
|
||||
{confirmButtonLabel}
|
||||
</Button>
|
||||
{onAlternative ? (
|
||||
<Button variant="primary" onClick={onAlternative}>
|
||||
{alternativeButtonLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
</Stack>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
text: css({
|
||||
fontSize: theme.typography.h5.fontSize,
|
||||
color: theme.colors.text.primary,
|
||||
}),
|
||||
description: css({
|
||||
fontSize: theme.typography.body.fontSize,
|
||||
}),
|
||||
confirmationInput: css({
|
||||
paddingTop: theme.spacing(1),
|
||||
}),
|
||||
buttonsContainer: css({
|
||||
paddingTop: theme.spacing(3),
|
||||
}),
|
||||
});
|
@ -1,18 +1,14 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { IconName } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Box } from '../Layout/Box/Box';
|
||||
import { Stack } from '../Layout/Stack/Stack';
|
||||
import { ButtonVariant } from '../Button';
|
||||
import { Modal } from '../Modal/Modal';
|
||||
|
||||
import { ConfirmContent } from './ConfirmContent';
|
||||
|
||||
export interface ConfirmModalProps {
|
||||
/** Toggle modal's open/closed state */
|
||||
isOpen: boolean;
|
||||
@ -68,89 +64,29 @@ export const ConfirmModal = ({
|
||||
onAlternative,
|
||||
confirmButtonVariant = 'destructive',
|
||||
}: ConfirmModalProps): JSX.Element => {
|
||||
const [disabled, setDisabled] = useState(Boolean(confirmationText));
|
||||
const styles = useStyles2(getStyles);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const onConfirmationTextChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
setDisabled(confirmationText?.toLowerCase().localeCompare(event.currentTarget.value.toLowerCase()) !== 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// for some reason autoFocus property did no work on this button, but this does
|
||||
if (isOpen) {
|
||||
buttonRef.current?.focus();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setDisabled(Boolean(confirmationText));
|
||||
}
|
||||
}, [isOpen, confirmationText]);
|
||||
|
||||
const onConfirmClick = async () => {
|
||||
setDisabled(true);
|
||||
try {
|
||||
await onConfirm();
|
||||
} finally {
|
||||
setDisabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { handleSubmit } = useForm();
|
||||
|
||||
return (
|
||||
<Modal className={cx(styles.modal, modalClass)} title={title} icon={icon} isOpen={isOpen} onDismiss={onDismiss}>
|
||||
<form onSubmit={handleSubmit(onConfirmClick)}>
|
||||
<div className={styles.modalText}>
|
||||
{body}
|
||||
{description ? <div className={styles.modalDescription}>{description}</div> : null}
|
||||
{confirmationText ? (
|
||||
<div className={styles.modalConfirmationInput}>
|
||||
<Stack alignItems="flex-start">
|
||||
<Box>
|
||||
<Input placeholder={`Type "${confirmationText}" to confirm`} onChange={onConfirmationTextChange} />
|
||||
</Box>
|
||||
</Stack>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Modal.ButtonRow>
|
||||
<Button variant={dismissVariant} onClick={onDismiss} fill="outline">
|
||||
{dismissText}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant={confirmButtonVariant}
|
||||
disabled={disabled}
|
||||
ref={buttonRef}
|
||||
data-testid={selectors.pages.ConfirmModal.delete}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
{onAlternative ? (
|
||||
<Button variant="primary" onClick={onAlternative}>
|
||||
{alternativeText}
|
||||
</Button>
|
||||
) : null}
|
||||
</Modal.ButtonRow>
|
||||
</form>
|
||||
<ConfirmContent
|
||||
body={body}
|
||||
description={description}
|
||||
confirmButtonLabel={confirmText}
|
||||
dismissButtonLabel={dismissText}
|
||||
dismissButtonVariant={dismissVariant}
|
||||
confirmPromptText={confirmationText}
|
||||
alternativeButtonLabel={alternativeText}
|
||||
confirmButtonVariant={confirmButtonVariant}
|
||||
onConfirm={onConfirm}
|
||||
onDismiss={onDismiss}
|
||||
onAlternative={onAlternative}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
const getStyles = () => ({
|
||||
modal: css({
|
||||
width: '500px',
|
||||
}),
|
||||
modalText: css({
|
||||
fontSize: theme.typography.h5.fontSize,
|
||||
color: theme.colors.text.primary,
|
||||
}),
|
||||
modalDescription: css({
|
||||
fontSize: theme.typography.body.fontSize,
|
||||
}),
|
||||
modalConfirmationInput: css({
|
||||
paddingTop: theme.spacing(1),
|
||||
}),
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user