Skip to content

Commit

Permalink
feat(ENG-692): add OakModal
Browse files Browse the repository at this point in the history
  • Loading branch information
carlmw committed Mar 27, 2024
1 parent 00bf7bb commit 85df078
Show file tree
Hide file tree
Showing 12 changed files with 1,371 additions and 11 deletions.
452 changes: 441 additions & 11 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@storybook/addon-onboarding": "^1.0.10",
"@storybook/blocks": "^7.6.7",
"@storybook/nextjs": "^7.6.7",
"@storybook/preview-api": "^8.0.4",
"@storybook/react": "^7.6.7",
"@storybook/testing-library": "^0.2.2",
"@testing-library/jest-dom": "^6.2.0",
Expand All @@ -61,6 +62,7 @@
"@types/jest": "^29.5.11",
"@types/react": "^18.2.47",
"@types/react-test-renderer": "^18.0.7",
"@types/react-transition-group": "^4.4.10",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
Expand All @@ -80,7 +82,9 @@
"prettier": "^3.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-focus-on": "^3.9.2",
"react-test-renderer": "^18.2.0",
"react-transition-group": "^4.4.5",
"rollup": "^4.9.4",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
Expand Down
131 changes: 131 additions & 0 deletions src/components/molecules/OakModal/OakModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Meta, StoryObj } from "@storybook/react";
import { useArgs } from "@storybook/preview-api";
import React, { Fragment } from "react";

import { OakSecondaryButton } from "../OakSecondaryButton";
import { OakPrimaryButton } from "../OakPrimaryButton";
import { OakTextInput } from "../OakTextInput";

import { OakModal } from "./OakModal";
import { OakModalFooter } from "./OakModalFooter";
import { OakModalBody } from "./OakModalBody";

import { OakBox, OakP } from "@/components/atoms";

const meta: Meta<typeof OakModal> = {
component: OakModal,
tags: ["autodocs"],
title: "components/molecules/OakModal",
argTypes: {
children: {
control: { type: "text" },
},
footerSlot: {
control: { type: "text" },
},
},
parameters: {
controls: {
include: ["children", "footerSlot", "isOpen", "onClose"],
},
},
args: {
"aria-label": "Example modal",
"aria-description": "This is an example modal",
children: (
<OakModalBody>
<OakBox $mb="space-between-m">Modal content</OakBox>
<OakTextInput />
</OakModalBody>
),
footerSlot: (
<OakModalFooter>
<OakSecondaryButton width="100%">Secondary action</OakSecondaryButton>
<OakPrimaryButton width="100%">Primary action</OakPrimaryButton>
</OakModalFooter>
),
},
render: (args) => {
const [, updateArgs] = useArgs();
const onClose = () => updateArgs({ isOpen: false });
const onOpen = () => updateArgs({ isOpen: true });

return (
<>
<OakSecondaryButton onClick={onOpen}>Open modal</OakSecondaryButton>
<OakModal {...args} onClose={onClose} />
</>
);
},
};
export default meta;

type Story = StoryObj<typeof OakModal>;

export const Default: Story = {};

export const WithScrolling: Story = {
args: {
"aria-label": "Scrolling modal",
"aria-description": "A modal with scrolling content",
children: (
<OakModalBody>
{[1, 2, 3].map((i) => (
<Fragment key={i}>
<OakP>
In today's digital landscape, privacy concerns have become
increasingly paramount, prompting the need for transparency and
user consent regarding data collection practices. A cookie banner
serves as the initial point of contact between a website and its
visitors, notifying them of the site's use of cookies and other
tracking technologies. This notification is essential in fostering
trust and ensuring compliance with privacy regulations such as the
General Data Protection Regulation (GDPR) in the European Union
and the California Consumer Privacy Act (CCPA) in the United
States.
</OakP>
<OakP>
Without a cookie banner, website visitors may remain unaware of
the data being collected about them, including their browsing
habits, preferences, and demographic information. This lack of
transparency can erode trust and lead to potential legal
repercussions if users' privacy rights are violated. By
prominently displaying a cookie banner, websites demonstrate their
commitment to respecting user privacy and empower visitors to make
informed choices about their online data.
</OakP>
<OakP>
Furthermore, cookie banners not only inform users about the
presence of cookies but also provide options for managing cookie
preferences. Through these banners, visitors can choose to accept
or reject certain types of cookies, such as those used for
tracking or advertising purposes. This level of control empowers
users to tailor their browsing experience according to their
privacy preferences, enhancing their sense of agency and control
over their personal data.
</OakP>
<OakP>
In addition to legal compliance and user empowerment, cookie
banners also play a crucial role in enhancing user experience. By
providing clear and concise information about cookies, websites
can minimize confusion and frustration among visitors. This
transparency fosters a positive user experience by promoting trust
and reducing the likelihood of unexpected data collection,
ultimately contributing to increased user engagement and loyalty.
</OakP>
<OakP>
In summary, the need for a cookie banner on a website is
multifaceted. Not only does it ensure compliance with privacy
regulations and mitigate legal risks, but it also fosters trust,
empowers users, and enhances overall user experience. By
proactively addressing privacy concerns and providing transparent
information about data collection practices, websites can build
stronger relationships with their visitors and establish
themselves as trustworthy stewards of user data.
</OakP>
</Fragment>
))}
</OakModalBody>
),
},
};
69 changes: 69 additions & 0 deletions src/components/molecules/OakModal/OakModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { create } from "react-test-renderer";
import React, { ReactNode } from "react";
import "@testing-library/jest-dom";
import { act, fireEvent } from "@testing-library/react";

import { OakModal } from "./OakModal";

import renderWithTheme from "@/test-helpers/renderWithTheme";
import { oakDefaultTheme } from "@/styles";
import { OakThemeProvider } from "@/components/atoms";

jest.mock("react-dom", () => {
return {
...jest.requireActual("react-dom"),
createPortal: (node: ReactNode) => node,
};
});

global.IntersectionObserver =
global.IntersectionObserver ??
class IntersectionObserver {
constructor() {}
observe() {
return null;
}
disconnect() {
return null;
}
};

describe(OakModal, () => {
it("matches snapshot", () => {
const tree = create(
<OakThemeProvider theme={oakDefaultTheme}>
<OakModal isOpen onClose={() => {}} footerSlot="Modal footer">
Modal content
</OakModal>
</OakThemeProvider>,
).toJSON();

expect(tree).toMatchSnapshot();
});

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

const { getByLabelText } = renderWithTheme(
<OakModal isOpen onClose={onCloseSpy}>
Modal content
</OakModal>,
);

act(() => {
fireEvent.click(getByLabelText("Close"));
});

expect(onCloseSpy).toHaveBeenCalled();
});

it("gives the first focusable element in the modal body focus", () => {
const { getByRole } = renderWithTheme(
<OakModal isOpen onClose={() => {}}>
<input type="text" />
</OakModal>,
);

expect(getByRole("textbox")).toHaveFocus();
});
});
Loading

0 comments on commit 85df078

Please sign in to comment.