From 03a000e1b5e0145d3aeb50d4243e9e02bd71d73d Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Mon, 10 Jun 2024 11:14:25 -0300 Subject: [PATCH] ConfirmModal: Reuse confirm content (#88577) --- .../ConfirmModal/ConfirmContent.tsx | 140 ++++++++++++++++++ .../components/ConfirmModal/ConfirmModal.tsx | 102 +++---------- 2 files changed, 159 insertions(+), 83 deletions(-) create mode 100644 packages/grafana-ui/src/components/ConfirmModal/ConfirmContent.tsx diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmContent.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmContent.tsx new file mode 100644 index 00000000000..016df971c2b --- /dev/null +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmContent.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; + /** Confirm action callback + * Return a promise to disable the confirm button until the promise is resolved + */ + onConfirm(): void | Promise; + /** 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(null); + + const onConfirmationTextChange = (event: React.FormEvent) => { + 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 ( +
+
+ {body} + {description ?
{description}
: null} + {confirmPromptText ? ( +
+ + + + + +
+ ) : null} +
+
+ + + + {onAlternative ? ( + + ) : null} + +
+
+ ); +}; + +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), + }), +}); diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx index 80c9b2f41c8..070c9db9d75 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx @@ -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(null); - const onConfirmationTextChange = (event: React.FormEvent) => { - 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 ( -
-
- {body} - {description ?
{description}
: null} - {confirmationText ? ( -
- - - - - -
- ) : null} -
- - - - {onAlternative ? ( - - ) : null} - -
+
); }; -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), - }), });