Skip to content

Commit 4999a84

Browse files
authored
feat(ModalTopActions): top actions component for internal use (#2415)
1 parent 3f8faa6 commit 4999a84

File tree

4 files changed

+132
-0
lines changed

4 files changed

+132
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.actions {
2+
position: absolute;
3+
right: var(--spacing-large);
4+
top: var(--spacing-large);
5+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react";
2+
import styles from "./ModalTopActions.module.scss";
3+
import { ModalTopActionsButtonColor, ModalTopActionsColor, ModalTopActionsProps } from "./ModalTopActions.types";
4+
import Flex from "../../Flex/Flex";
5+
import IconButton from "../../IconButton/IconButton";
6+
import { CloseSmall } from "../../Icon/Icons";
7+
import { ButtonColor } from "../../Button/ButtonConstants";
8+
9+
const colorToButtonColor: Record<ModalTopActionsColor, ModalTopActionsButtonColor> = {
10+
dark: ButtonColor.ON_INVERTED_BACKGROUND,
11+
light: ButtonColor.ON_PRIMARY_COLOR
12+
};
13+
14+
const ModalTopActions = ({ renderAction, color, closeButtonAriaLabel, onClose }: ModalTopActionsProps) => {
15+
const buttonColor = colorToButtonColor[color] || ButtonColor.PRIMARY;
16+
17+
return (
18+
<Flex className={styles.actions}>
19+
{typeof renderAction === "function" ? renderAction(buttonColor) : renderAction}
20+
<IconButton
21+
icon={CloseSmall}
22+
onClick={onClose}
23+
size={IconButton.sizes.SMALL}
24+
kind={IconButton.kinds.TERTIARY}
25+
color={buttonColor}
26+
ariaLabel={closeButtonAriaLabel}
27+
/>
28+
</Flex>
29+
);
30+
};
31+
32+
export default ModalTopActions;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from "react";
2+
import MenuButton from "../../MenuButton/MenuButton";
3+
import IconButton from "../../IconButton/IconButton";
4+
import { ButtonColor } from "../../Button/ButtonConstants";
5+
6+
export type ModalTopActionsColor = "light" | "dark";
7+
export type ModalTopActionsButtonColor =
8+
| ButtonColor.PRIMARY
9+
| ButtonColor.ON_PRIMARY_COLOR
10+
| ButtonColor.ON_INVERTED_BACKGROUND;
11+
12+
export interface ModalTopActionsProps {
13+
/**
14+
* action can be passed either as a function or direct
15+
* it allows passing back to consumer the color he chose, so he won't have to define it twice
16+
*/
17+
renderAction?:
18+
| React.ReactElement<typeof MenuButton | typeof IconButton>
19+
| ((color?: ModalTopActionsButtonColor) => React.ReactElement<typeof MenuButton | typeof IconButton>);
20+
color?: ModalTopActionsColor;
21+
closeButtonAriaLabel?: string;
22+
onClose?: React.MouseEventHandler<HTMLDivElement>;
23+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from "react";
2+
import { render, fireEvent, within } from "@testing-library/react";
3+
import ModalTopActions from "../ModalTopActions";
4+
import IconButton from "../../../IconButton/IconButton";
5+
import { Feedback as FeedbackIcon } from "../../../Icon/Icons";
6+
import { ButtonColor } from "../../../Button/ButtonConstants";
7+
import { camelCase } from "lodash-es";
8+
9+
describe("ModalTopActions", () => {
10+
const closeButtonAriaLabel = "Close modal";
11+
12+
it("renders the close button with the correct aria-label", () => {
13+
const { getByLabelText } = render(<ModalTopActions closeButtonAriaLabel={closeButtonAriaLabel} />);
14+
15+
expect(getByLabelText(closeButtonAriaLabel)).toBeInTheDocument();
16+
});
17+
18+
it("calls onClose when the close button is clicked", () => {
19+
const mockOnClose = jest.fn();
20+
21+
const { getByLabelText } = render(
22+
<ModalTopActions onClose={mockOnClose} closeButtonAriaLabel={closeButtonAriaLabel} />
23+
);
24+
25+
fireEvent.click(getByLabelText(closeButtonAriaLabel));
26+
expect(mockOnClose).toHaveBeenCalled();
27+
});
28+
29+
it("does not fail when onClose is not provided", () => {
30+
const { getByLabelText } = render(<ModalTopActions closeButtonAriaLabel={closeButtonAriaLabel} />);
31+
fireEvent.click(getByLabelText(closeButtonAriaLabel));
32+
expect(() => getByLabelText(closeButtonAriaLabel)).not.toThrow();
33+
});
34+
35+
it("renders the action button using the renderAction prop as a function", () => {
36+
const renderAction = jest.fn(color => <IconButton data-testid="extra-action" icon={FeedbackIcon} color={color} />);
37+
const { getByTestId } = render(<ModalTopActions renderAction={renderAction} />);
38+
39+
expect(within(getByTestId("extra-action")).getByTestId("icon")).toBeInTheDocument();
40+
});
41+
42+
it("calls renderAction with correct color argument", () => {
43+
const renderAction = jest.fn(color => <IconButton data-testid="extra-action" icon={FeedbackIcon} color={color} />);
44+
render(<ModalTopActions color="dark" renderAction={renderAction} />);
45+
46+
expect(renderAction).toHaveBeenCalledWith(ButtonColor.ON_INVERTED_BACKGROUND);
47+
});
48+
49+
it("renders the action button using the renderAction prop directly", () => {
50+
const renderAction = (
51+
<IconButton data-testid="extra-action" icon={FeedbackIcon} color={IconButton.colors.ON_PRIMARY_COLOR} />
52+
);
53+
const { getByTestId } = render(<ModalTopActions renderAction={renderAction} />);
54+
55+
expect(within(getByTestId("extra-action")).getByTestId("icon")).toBeInTheDocument();
56+
});
57+
58+
it("applies the correct color when 'dark' is passed", () => {
59+
const { getByLabelText } = render(<ModalTopActions color="dark" closeButtonAriaLabel={closeButtonAriaLabel} />);
60+
expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_INVERTED_BACKGROUND));
61+
});
62+
63+
it("applies the correct color when 'light' is passed", () => {
64+
const { getByLabelText } = render(<ModalTopActions color="light" closeButtonAriaLabel={closeButtonAriaLabel} />);
65+
expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_PRIMARY_COLOR));
66+
});
67+
68+
it("applies the default color when no color is passed", () => {
69+
const { getByLabelText } = render(<ModalTopActions closeButtonAriaLabel={closeButtonAriaLabel} />);
70+
expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.PRIMARY));
71+
});
72+
});

0 commit comments

Comments
 (0)