Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #18 from storybookjs/valentin/implement-modal
Browse files Browse the repository at this point in the history
Implement bare minimum modal component
  • Loading branch information
valentinpalkovic authored May 30, 2023
2 parents ee9656b + 0c6aa80 commit 557821b
Show file tree
Hide file tree
Showing 5 changed files with 615 additions and 4 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@storybook/addon-essentials": "^7.0.0",
"@storybook/addon-interactions": "^7.0.0",
"@storybook/addon-links": "^7.0.0",
"@storybook/jest": "^0.1.0",
"@storybook/react": "^7.0.0",
"@storybook/react-vite": "^7.0.0",
"@storybook/testing-library": "^0.0.14-next.1",
Expand Down Expand Up @@ -109,6 +110,7 @@
"readme": "ERROR: No README data found!",
"homepage": "https://github.com/storybookjs/addon-onboarding#readme",
"dependencies": {
"@radix-ui/react-dialog": "^1.0.4",
"react-confetti": "^6.1.0"
}
}
45 changes: 45 additions & 0 deletions src/components/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Meta, StoryObj } from "@storybook/react";
import { Modal } from "./Modal";
import React, { useState } from "react";
import { userEvent, within } from "@storybook/testing-library";
import { expect } from "@storybook/jest";

const meta: Meta<typeof Modal> = {
component: Modal,
decorators: [
(storyFn) => (
<div style={{ width: "1200px", height: "800px" }}>{storyFn()}</div>
),
],
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => {
const [isOpen, setOpen] = useState(false);
return (
<>
<Modal width="740px" open={isOpen}>
{({ Title, Description, Close }) => (
<>
Hello world
<Title>Modal Title</Title>
<Description>Modal Description</Description>
<Close onClick={() => setOpen(false)}>Close</Close>
</>
)}
</Modal>
<button onClick={() => setOpen(true)}>Open modal</button>
</>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement.parentElement);
const button = canvas.getByText("Open modal");
await userEvent.click(button);
await expect(canvas.findByText("Hello world")).resolves.toBeInTheDocument();
},
};
46 changes: 46 additions & 0 deletions src/components/Modal/Modal.styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { css, styled } from "@storybook/theming";
import {
Overlay,
Content,
Title,
Description,
Close,
} from "@radix-ui/react-dialog";
import React from "react";

export const StyledOverlay = styled(Overlay)`
background-color: rgba(0, 0, 0, 0.25);
position: fixed;
inset: 0px;
width: 100%;
height: 100%;
})`;

export const StyledContent = styled.div<{ width: string }>(
({ width }) => css`
background-color: white;
border-radius: 6px;
box-shadow: rgba(14, 18, 22, 0.35) 0px 10px 38px -10px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: ${width ?? "calc(100% - 40px)"};
max-width: calc(100% - 40px);
max-height: 85vh;
`
);

export const ContentWrapper = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof StyledContent> &
React.ComponentProps<typeof Content>
>(({ width, children, ...contentProps }, ref) => (
<Content ref={ref} asChild {...contentProps}>
<StyledContent width={width}>{children}</StyledContent>
</Content>
));

export const StyledTitle = styled(Title)``;
export const StyledDescription = styled(Description)``;
export const StyledClose = styled(Close)``;
51 changes: 51 additions & 0 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from "react";

import * as Dialog from "@radix-ui/react-dialog";
import {
ContentWrapper,
StyledClose,
StyledDescription,
StyledOverlay,
StyledTitle,
} from "./Modal.styled";

type ContentProps = React.ComponentProps<typeof ContentWrapper>;

interface ModalProps
extends Omit<React.ComponentProps<typeof Dialog.Root>, "children"> {
width?: string;
children: (props: {
Title: typeof StyledTitle;
Description: typeof StyledDescription;
Close: typeof StyledClose;
}) => React.ReactNode;
onEscapeKeyDown?: ContentProps["onEscapeKeyDown"];
onInteractOutside?: ContentProps["onInteractOutside"];
}

export function Modal({
children,
width,
onEscapeKeyDown,
onInteractOutside = (ev) => ev.preventDefault(),
...rootProps
}: ModalProps) {
return (
<Dialog.Root {...rootProps}>
<Dialog.Portal>
<StyledOverlay />
<ContentWrapper
width={width}
onInteractOutside={onInteractOutside}
onEscapeKeyDown={onEscapeKeyDown}
>
{children({
Title: StyledTitle,
Description: StyledDescription,
Close: StyledClose,
})}
</ContentWrapper>
</Dialog.Portal>
</Dialog.Root>
);
}
Loading

0 comments on commit 557821b

Please sign in to comment.