Skip to content

Commit

Permalink
feat(ModalTopActions): top actions component for internal use (#2415)
Browse files Browse the repository at this point in the history
  • Loading branch information
YossiSaadi committed Oct 14, 2024
1 parent 66d900c commit 3e6faca
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.actions {
position: absolute;
right: var(--spacing-large);
top: var(--spacing-large);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
import styles from "./ModalTopActions.module.scss";
import { ModalTopActionsButtonColor, ModalTopActionsColor, ModalTopActionsProps } from "./ModalTopActions.types";
import Flex from "../../Flex/Flex";
import IconButton from "../../IconButton/IconButton";
import { CloseSmall } from "../../Icon/Icons";
import { ButtonColor } from "../../Button/ButtonConstants";

const colorToButtonColor: Record<ModalTopActionsColor, ModalTopActionsButtonColor> = {
dark: ButtonColor.ON_INVERTED_BACKGROUND,
light: ButtonColor.ON_PRIMARY_COLOR
};

const ModalTopActions = ({ renderAction, color, closeButtonAriaLabel, onClose }: ModalTopActionsProps) => {
const buttonColor = colorToButtonColor[color] || ButtonColor.PRIMARY;

return (
<Flex className={styles.actions}>
{typeof renderAction === "function" ? renderAction(buttonColor) : renderAction}
<IconButton
icon={CloseSmall}
onClick={onClose}
size={IconButton.sizes.SMALL}
kind={IconButton.kinds.TERTIARY}
color={buttonColor}
ariaLabel={closeButtonAriaLabel}
/>
</Flex>
);
};

export default ModalTopActions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import MenuButton from "../../MenuButton/MenuButton";
import IconButton from "../../IconButton/IconButton";
import { ButtonColor } from "../../Button/ButtonConstants";

export type ModalTopActionsColor = "light" | "dark";
export type ModalTopActionsButtonColor =
| ButtonColor.PRIMARY
| ButtonColor.ON_PRIMARY_COLOR
| ButtonColor.ON_INVERTED_BACKGROUND;

export interface ModalTopActionsProps {
/**
* action can be passed either as a function or direct
* it allows passing back to consumer the color he chose, so he won't have to define it twice
*/
renderAction?:
| React.ReactElement<typeof MenuButton | typeof IconButton>
| ((color?: ModalTopActionsButtonColor) => React.ReactElement<typeof MenuButton | typeof IconButton>);
color?: ModalTopActionsColor;
closeButtonAriaLabel?: string;
onClose?: React.MouseEventHandler<HTMLDivElement>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from "react";
import { render, fireEvent, within } from "@testing-library/react";
import ModalTopActions from "../ModalTopActions";
import IconButton from "../../../IconButton/IconButton";
import { Feedback as FeedbackIcon } from "../../../Icon/Icons";
import { ButtonColor } from "../../../Button/ButtonConstants";
import { camelCase } from "lodash-es";

describe("ModalTopActions", () => {
const closeButtonAriaLabel = "Close modal";

it("renders the close button with the correct aria-label", () => {
const { getByLabelText } = render(<ModalTopActions closeButtonAriaLabel={closeButtonAriaLabel} />);

expect(getByLabelText(closeButtonAriaLabel)).toBeInTheDocument();
});

it("calls onClose when the close button is clicked", () => {
const mockOnClose = jest.fn();

const { getByLabelText } = render(
<ModalTopActions onClose={mockOnClose} closeButtonAriaLabel={closeButtonAriaLabel} />
);

fireEvent.click(getByLabelText(closeButtonAriaLabel));
expect(mockOnClose).toHaveBeenCalled();
});

it("does not fail when onClose is not provided", () => {
const { getByLabelText } = render(<ModalTopActions closeButtonAriaLabel={closeButtonAriaLabel} />);
fireEvent.click(getByLabelText(closeButtonAriaLabel));
expect(() => getByLabelText(closeButtonAriaLabel)).not.toThrow();
});

it("renders the action button using the renderAction prop as a function", () => {
const renderAction = jest.fn(color => <IconButton data-testid="extra-action" icon={FeedbackIcon} color={color} />);
const { getByTestId } = render(<ModalTopActions renderAction={renderAction} />);

expect(within(getByTestId("extra-action")).getByTestId("icon")).toBeInTheDocument();
});

it("calls renderAction with correct color argument", () => {
const renderAction = jest.fn(color => <IconButton data-testid="extra-action" icon={FeedbackIcon} color={color} />);
render(<ModalTopActions color="dark" renderAction={renderAction} />);

expect(renderAction).toHaveBeenCalledWith(ButtonColor.ON_INVERTED_BACKGROUND);
});

it("renders the action button using the renderAction prop directly", () => {
const renderAction = (
<IconButton data-testid="extra-action" icon={FeedbackIcon} color={IconButton.colors.ON_PRIMARY_COLOR} />
);
const { getByTestId } = render(<ModalTopActions renderAction={renderAction} />);

expect(within(getByTestId("extra-action")).getByTestId("icon")).toBeInTheDocument();
});

it("applies the correct color when 'dark' is passed", () => {
const { getByLabelText } = render(<ModalTopActions color="dark" closeButtonAriaLabel={closeButtonAriaLabel} />);
expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_INVERTED_BACKGROUND));
});

it("applies the correct color when 'light' is passed", () => {
const { getByLabelText } = render(<ModalTopActions color="light" closeButtonAriaLabel={closeButtonAriaLabel} />);
expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_PRIMARY_COLOR));
});

it("applies the default color when no color is passed", () => {
const { getByLabelText } = render(<ModalTopActions closeButtonAriaLabel={closeButtonAriaLabel} />);
expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.PRIMARY));
});
});

0 comments on commit 3e6faca

Please sign in to comment.