diff --git a/src/common/components/Dialog/Backdrop.tsx b/src/common/components/Dialog/Backdrop.tsx new file mode 100644 index 0000000..3549bdc --- /dev/null +++ b/src/common/components/Dialog/Backdrop.tsx @@ -0,0 +1,46 @@ +import { PropsWithChildren } from 'react'; +import { BaseComponentProps } from '@leanstacks/react-common'; +import classNames from 'classnames'; + +/** + * Properties for the `Backdrop` component. + * @param {function} [onClick] - Optional. A function called when clicked. + * @see {@link BaseComponentProps} + * @see {@link PropsWithChildren} + */ +interface BackdropProps extends BaseComponentProps, PropsWithChildren { + onClick?: (e: React.MouseEvent) => void | Promise; +} + +/** + * The `Backdrop` component renders a semi-opaque background for another + * component. Usually used to partially mask background content to draw + * attention to content in the foreground such as a `Dialog` or `Menu`. + * @param {BackgroundProps} props - Component properties + * @returns {JSX.Element} JSX + */ +const Backdrop = ({ + children, + className, + onClick, + testId = 'backdrop', +}: BackdropProps): JSX.Element => { + const handleClick = (e: React.MouseEvent) => { + onClick?.(e); + }; + + return ( +
+ {children} +
+ ); +}; + +export default Backdrop; diff --git a/src/common/components/Dialog/Dialog.tsx b/src/common/components/Dialog/Dialog.tsx new file mode 100644 index 0000000..19e9817 --- /dev/null +++ b/src/common/components/Dialog/Dialog.tsx @@ -0,0 +1,69 @@ +import { PropsWithChildren, useEffect, useState } from 'react'; +import { BaseComponentProps } from '@leanstacks/react-common'; +import classNames from 'classnames'; + +import Backdrop from './Backdrop'; + +/** + * Properties for the `Dialog` component. + * @param {boolean} [isOpen] - Indicates if the Dialog should be displayed. + * @param {function} [onClose] - A function called when the Dialog closes. + * @see {@link BaseComponentProps} + * @see {@link PropsWithChildren} + */ +export interface DialogProps extends BaseComponentProps, PropsWithChildren { + isOpen?: boolean; + onClose?: () => void | Promise; +} + +/** + * A `Dialog` is a modal window that displays on top of the main content, + * typically asking the user to take an action or confirm a decision. + * @param {DialogProps} props - Component properties. + * @returns {JSX.Element} JSX + */ +const Dialog = ({ + children, + className, + isOpen = false, + onClose, + testId = 'dialog', +}: DialogProps): JSX.Element => { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + useEffect(() => { + setIsDialogOpen(isOpen); + }, [isOpen]); + + const closeDialog = (): void => { + setIsDialogOpen(false); + onClose?.(); + }; + + const handleBackdropClick = (): void => { + closeDialog(); + }; + + const handleDialogClick = (e: React.MouseEvent): void => { + e.stopPropagation(); + }; + + return ( +
+ +
+ {children} +
+
+
+ ); +}; + +export default Dialog; diff --git a/src/common/components/Dialog/DialogButton.tsx b/src/common/components/Dialog/DialogButton.tsx new file mode 100644 index 0000000..cef0a65 --- /dev/null +++ b/src/common/components/Dialog/DialogButton.tsx @@ -0,0 +1,45 @@ +import { Button, ButtonProps, ButtonVariant } from '@leanstacks/react-common'; +import classNames from 'classnames'; + +/** + * Variations of the `DialogButton`. + */ +type DialogButtonVariant = 'primary' | 'secondary' | 'danger'; + +/** + * Properties for the `DialogButton` component. + * @param {DialogButtonVariant} variant - The variant. + * @see {@link ButtonProps} + */ +interface DialogButtonProps extends Omit { + variant?: DialogButtonVariant; +} + +/** + * The `DialogButton` is a type of `Button` specifically styled for use + * within a `Dialog`. + * @param {DialogButtonProps} props - Component properties. + * @returns {JSX.Element} JSX + */ +const DialogButton = ({ + className, + variant = 'secondary', + testId = 'dialog-button', + ...buttonProps +}: DialogButtonProps): JSX.Element => { + return ( + +