Skip to content

Commit

Permalink
feat(ModalFooter): allow tooltip for the modal footer's buttons (#2754)
Browse files Browse the repository at this point in the history
  • Loading branch information
YossiSaadi authored Feb 9, 2025
1 parent d69763f commit 9f45266
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React, { forwardRef } from "react";
import cx from "classnames";
import ModalFooterBase from "../ModalFooterBase/ModalFooterBase";
import { getTestId } from "../../../../tests/test-ids-utils";
import { ComponentDefaultTestId } from "../../../../tests/constants";
import { ModalFooterProps } from "./ModalFooter.types";
import styles from "./ModalFooter.module.scss";
import { getPropsForButton } from "../utils/getPropsForButton";

const ModalFooter = forwardRef(
(
{ primaryButton, secondaryButton, renderSideAction, "data-testid": dataTestId, className, id }: ModalFooterProps,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const primary = { ...primaryButton, className: cx(primaryButton.className, styles.primary) };
const secondary = secondaryButton
? { ...secondaryButton, className: cx(secondaryButton.className, styles.secondary) }
: undefined;
const primary = getPropsForButton(primaryButton, styles.primary);
const secondary = getPropsForButton(secondaryButton, styles.secondary);

return (
<ModalFooterBase
ref={ref}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ import Button from "../../../Button/Button";
import Flex from "../../../Flex/Flex";
import { ModalFooterBaseProps } from "./ModalFooterBase.types";
import cx from "classnames";
import { Tooltip } from "../../../Tooltip";

const ModalFooterBase = forwardRef(
(
{ primaryButton, secondaryButton, renderAction, id, className, "data-testid": dataTestId }: ModalFooterBaseProps,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const { text: primaryButtonText, ...primaryButtonProps } = primaryButton;
const { text: secondaryButtonText, ...secondaryButtonProps } = secondaryButton || {};
const {
text: primaryButtonText,
tooltipProps: primaryButtonTooltipProps = {},
...primaryButtonProps
} = primaryButton;
const {
text: secondaryButtonText,
tooltipProps: secondaryButtonTooltipProps = {},
...secondaryButtonProps
} = secondaryButton || {};

return (
<Flex
ref={ref}
Expand All @@ -21,11 +31,15 @@ const ModalFooterBase = forwardRef(
className={cx(styles.footer, className)}
data-testid={dataTestId}
>
<Button {...primaryButtonProps}>{primaryButtonText}</Button>
<Tooltip {...primaryButtonTooltipProps} content={primaryButtonTooltipProps.content}>
<Button {...primaryButtonProps}>{primaryButtonText}</Button>
</Tooltip>
{secondaryButton && (
<Button {...secondaryButtonProps} kind="tertiary">
{secondaryButtonText}
</Button>
<Tooltip {...secondaryButtonTooltipProps} content={secondaryButtonTooltipProps.content}>
<Button {...secondaryButtonProps} kind="tertiary">
{secondaryButtonText}
</Button>
</Tooltip>
)}
{renderAction}
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ButtonProps } from "../../../Button";
import React from "react";
import { VibeComponentProps } from "../../../../types";
import { TooltipProps } from "../../../Tooltip";

export interface ModalFooterActionProps extends Omit<ButtonProps, "children" | "kind" | "size"> {
/**
* Text to display as the Button's content.
*/
text: string;
/**
* Use when there's a need to display a tooltip on the button (e.g., explain why disabled).
*/
tooltipProps?: Partial<TooltipProps>;
}

export interface ModalFooterBaseProps extends VibeComponentProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { forwardRef } from "react";
import cx from "classnames";
import ModalFooterBase from "../ModalFooterBase/ModalFooterBase";
import { getTestId } from "../../../../tests/test-ids-utils";
import { ComponentDefaultTestId } from "../../../../tests/constants";
import styles from "./ModalFooterWizard.module.scss";
import { StepsGalleryHeader } from "../../../Steps/StepsGalleryHeader";
import { ModalFooterWizardProps } from "./ModalFooterWizard.types";
import { getPropsForButton } from "../utils/getPropsForButton";

const ModalFooterWizard = forwardRef(
(
Expand All @@ -21,10 +21,9 @@ const ModalFooterWizard = forwardRef(
}: ModalFooterWizardProps,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const primary = { ...primaryButton, className: cx(primaryButton.className, styles.primary) };
const secondary = secondaryButton
? { ...secondaryButton, className: cx(secondaryButton.className, styles.secondary) }
: undefined;
const primary = getPropsForButton(primaryButton, styles.primary);
const secondary = getPropsForButton(secondaryButton, styles.secondary);

const steps = (
<StepsGalleryHeader
stepsCount={stepCount}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getPropsForButton } from "../getPropsForButton";
import cx from "classnames";

describe("getPropsForButton", () => {
it("should return undefined when button is undefined", () => {
expect(getPropsForButton(undefined, "my-button-class")).toBeUndefined();
});

it("should apply buttonClassName to className when tooltipProps are not present", () => {
const result = getPropsForButton({ className: "existing-class", text: "Click Me" }, "my-button-class");
expect(result).toEqual({
text: "Click Me",
className: cx("existing-class", "my-button-class"),
tooltipProps: undefined
});
});

it("should apply buttonClassName to tooltipProps.referenceWrapperClassName when tooltipProps exist", () => {
const result = getPropsForButton(
{ text: "Click Me", tooltipProps: { content: "Hello", referenceWrapperClassName: "tooltip-class" } },
"my-button-class"
);
expect(result).toEqual({
text: "Click Me",
className: undefined,
tooltipProps: { content: "Hello", referenceWrapperClassName: cx("tooltip-class", "my-button-class") }
});
});

it("should keep className unchanged if tooltipProps exist", () => {
const result = getPropsForButton(
{ className: "existing-class", text: "Click Me", tooltipProps: { content: "Hello" } },
"my-button-class"
);
expect(result).toEqual({
text: "Click Me",
className: "existing-class",
tooltipProps: { content: "Hello", referenceWrapperClassName: "my-button-class" }
});
});

it("should attach the button className to button itself and return undefined tooltipProps if tooltip content is empty", () => {
const result = getPropsForButton(
{
className: "existing-class",
text: "Click Me",
tooltipProps: { referenceWrapperClassName: "original-tooltip-class", content: "" }
},
"my-button-class"
);
expect(result).toEqual({
text: "Click Me",
className: cx("existing-class", "my-button-class"),
tooltipProps: undefined
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ModalFooterActionProps } from "../ModalFooterBase/ModalFooterBase.types";
import cx from "classnames";

export function getPropsForButton(button?: ModalFooterActionProps, buttonClassName?: string) {
if (!button) return undefined;
const { tooltipProps, className, ...rest } = button;
return {
...rest,
className: tooltipProps?.content ? className : cx(className, buttonClassName),
tooltipProps: tooltipProps?.content
? { ...tooltipProps, referenceWrapperClassName: cx(tooltipProps.referenceWrapperClassName, buttonClassName) }
: undefined
};
}

0 comments on commit 9f45266

Please sign in to comment.