From 3e6faca0b16ee92f6f3f802bc94fadf74f608047 Mon Sep 17 00:00:00 2001 From: Yossi Saadi Date: Tue, 24 Sep 2024 14:35:18 +0300 Subject: [PATCH] feat(ModalTopActions): top actions component for internal use (#2415) --- .../ModalTopActions.module.scss | 5 ++ .../ModalTopActions/ModalTopActions.tsx | 32 +++++++++ .../ModalTopActions/ModalTopActions.types.ts | 23 ++++++ .../__tests__/ModalTopActions.test.tsx | 72 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.module.scss create mode 100644 packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx create mode 100644 packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts create mode 100644 packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx diff --git a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.module.scss b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.module.scss new file mode 100644 index 0000000000..25b099037a --- /dev/null +++ b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.module.scss @@ -0,0 +1,5 @@ +.actions { + position: absolute; + right: var(--spacing-large); + top: var(--spacing-large); +} diff --git a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx new file mode 100644 index 0000000000..0e44cf7b09 --- /dev/null +++ b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx @@ -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 = { + dark: ButtonColor.ON_INVERTED_BACKGROUND, + light: ButtonColor.ON_PRIMARY_COLOR +}; + +const ModalTopActions = ({ renderAction, color, closeButtonAriaLabel, onClose }: ModalTopActionsProps) => { + const buttonColor = colorToButtonColor[color] || ButtonColor.PRIMARY; + + return ( + + {typeof renderAction === "function" ? renderAction(buttonColor) : renderAction} + + + ); +}; + +export default ModalTopActions; diff --git a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts new file mode 100644 index 0000000000..a5a85c863f --- /dev/null +++ b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts @@ -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 + | ((color?: ModalTopActionsButtonColor) => React.ReactElement); + color?: ModalTopActionsColor; + closeButtonAriaLabel?: string; + onClose?: React.MouseEventHandler; +} diff --git a/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx b/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx new file mode 100644 index 0000000000..b1919f4105 --- /dev/null +++ b/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx @@ -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(); + + expect(getByLabelText(closeButtonAriaLabel)).toBeInTheDocument(); + }); + + it("calls onClose when the close button is clicked", () => { + const mockOnClose = jest.fn(); + + const { getByLabelText } = render( + + ); + + fireEvent.click(getByLabelText(closeButtonAriaLabel)); + expect(mockOnClose).toHaveBeenCalled(); + }); + + it("does not fail when onClose is not provided", () => { + const { getByLabelText } = render(); + 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 => ); + const { getByTestId } = render(); + + expect(within(getByTestId("extra-action")).getByTestId("icon")).toBeInTheDocument(); + }); + + it("calls renderAction with correct color argument", () => { + const renderAction = jest.fn(color => ); + render(); + + expect(renderAction).toHaveBeenCalledWith(ButtonColor.ON_INVERTED_BACKGROUND); + }); + + it("renders the action button using the renderAction prop directly", () => { + const renderAction = ( + + ); + const { getByTestId } = render(); + + expect(within(getByTestId("extra-action")).getByTestId("icon")).toBeInTheDocument(); + }); + + it("applies the correct color when 'dark' is passed", () => { + const { getByLabelText } = render(); + expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_INVERTED_BACKGROUND)); + }); + + it("applies the correct color when 'light' is passed", () => { + const { getByLabelText } = render(); + expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_PRIMARY_COLOR)); + }); + + it("applies the default color when no color is passed", () => { + const { getByLabelText } = render(); + expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.PRIMARY)); + }); +});