ConfirmModal: Reuse confirm content (#88577)

pull/88796/head^2
Juan Cabanas 12 months ago committed by GitHub
parent b30c81b1ad
commit 03a000e1b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 140
      packages/grafana-ui/src/components/ConfirmModal/ConfirmContent.tsx
  2. 102
      packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx

@ -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…
Cancel
Save