Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ModalTopActions): top actions component for internal use #2415

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,33 @@
import React from "react";
import styles from "./ModalTopActions.module.scss";
import { ModalTopActionsButtonColor, 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 ModalTopActions = ({ renderAction, color, closeButtonAriaLabel, onClose }: ModalTopActionsProps) => {
const buttonColor: ModalTopActionsButtonColor =
color === "dark"
? ButtonColor.ON_INVERTED_BACKGROUND
: color === "light"
? ButtonColor.ON_PRIMARY_COLOR
: ButtonColor.PRIMARY;
YossiSaadi marked this conversation as resolved.
Show resolved Hide resolved

return (
<Flex className={styles.actions}>
{/* this allows passing back to consumer the color he chose, so he won't have to define it twice */}
YossiSaadi marked this conversation as resolved.
Show resolved Hide resolved
{renderAction && renderAction(buttonColor)}
<IconButton
icon={CloseSmall}
onClick={onClose}
size={IconButton.sizes.SMALL}
kind={IconButton.kinds.TERTIARY}
color={buttonColor}
ariaLabel={closeButtonAriaLabel}
YossiSaadi marked this conversation as resolved.
Show resolved Hide resolved
/>
</Flex>
);
};

export default ModalTopActions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 {
renderAction?: (color?: ModalTopActionsButtonColor) => React.ReactElement<typeof MenuButton | typeof IconButton>;
Copy link
Contributor Author

@YossiSaadi YossiSaadi Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shlomitc suggests for it to be

Suggested change
renderAction?: (color?: ModalTopActionsButtonColor) => React.ReactElement<typeof MenuButton | typeof IconButton>;
renderAction?: React.ReactElement<typeof MenuButton | typeof IconButton>;

let's discuss it.
The main motivation for making it a function is the following:

// color on `renderHeaderAction` is "light"
<Modal closeButtonTheme="light" renderHeaderAction={(color) => <IconButton ...props color={color} />

vs

// force the user to type it twice - leaves a place for a mistake
<Modal closeButtonTheme="light" renderHeaderAction={<IconButton ...props color={"light"} />

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can support both, if you want the color you can use a function and use it in your component, otherwise you can control it yourself with just passing a component

color?: ModalTopActionsColor;
closeButtonAriaLabel?: string;
onClose?: React.MouseEventHandler<HTMLDivElement>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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", () => {
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("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));
});
});