Skip to content

Commit

Permalink
feat: dialog (#895)
Browse files Browse the repository at this point in the history
* feat: Dialog Component (#886)

* basic dialog component

---------

Co-authored-by: severinlandolt <sev.landolt@gmail.com>
Co-authored-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com>

* Update README.md

* Update dialog

* fix: dialog story

* fix: add max-w default to panel

* fix: classify background as theme token

---------

Co-authored-by: Touha Zohair <39002455+touha98@users.noreply.github.com>
Co-authored-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 7, 2024
1 parent 5700db9 commit 939f635
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,4 @@ We are always looking for new ideas or other ways to improve Tremor. If you have

[Apache License 2.0](https://github.com/tremorlabs/tremor/blob/main/License)

Copyright &copy; 2023 Tremor. All rights reserved.
Copyright &copy; 2024 Tremor. All rights reserved.
52 changes: 52 additions & 0 deletions src/components/layout-elements/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import { Dialog as HeadlessuiDialog, Transition } from "@headlessui/react";
import { makeClassName, tremorTwMerge } from "lib";

const makeDisplayClassName = makeClassName("dialog");

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;

export type DialogProps = React.HTMLAttributes<HTMLDivElement> & {
open: boolean;
onClose: (val: boolean) => void;
} & XOR<{ unmount?: boolean }, { static?: boolean }>;

const Dialog = React.forwardRef<HTMLDivElement, DialogProps>((props, ref) => {
const { children, className, ...other } = props;

return (
<Transition as={React.Fragment} appear show={props.open}>
<HeadlessuiDialog
as="div"
ref={ref}
{...other}
className={tremorTwMerge(makeDisplayClassName("root"), "relative z-50", className)}
>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className={tremorTwMerge(
"fixed inset-0 bg-tremor-content dark:bg-dark-tremor-content bg-opacity-75 transition-opacity",
)}
></div>
</Transition.Child>

<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4">{children}</div>
</div>
</HeadlessuiDialog>
</Transition>
);
});

Dialog.displayName = "Dialog";

export default Dialog;
48 changes: 48 additions & 0 deletions src/components/layout-elements/Dialog/DialogPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { Dialog as HeadlessuiDialog, Transition } from "@headlessui/react";
import { makeClassName, tremorTwMerge } from "lib";
import { RootStylesContext } from "contexts";

const makeDisplayClassName = makeClassName("dialog");

export type DialogPanelProps = React.HTMLAttributes<HTMLDivElement>;

const DialogPanel = React.forwardRef<HTMLDivElement, DialogPanelProps>((props, ref) => {
const { children, className, ...other } = props;
const rootStyles =
React.useContext(RootStylesContext) ?? tremorTwMerge("rounded-tremor-default p-6");

return (
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<HeadlessuiDialog.Panel
ref={ref}
className={tremorTwMerge(
makeDisplayClassName("panel"),
// light
"bg-tremor-background text-tremor-content ring-tremor-ring",
// dark
"dark:bg-dark-tremor-background dark:text-dark-tremor-content dark:ring-dark-tremor-ring",
// common
"max-w-lg overflow-hidden text-left shadow-xl ring-1 shadow-tremor transition-all transform",
rootStyles,
className,
)}
{...other}
>
{children}
</HeadlessuiDialog.Panel>
</Transition.Child>
);
});

DialogPanel.displayName = "DialogPanel";

export default DialogPanel;
2 changes: 2 additions & 0 deletions src/components/layout-elements/Dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Dialog, type DialogProps } from "./Dialog";
export { default as DialogPanel, type DialogPanelProps } from "./DialogPanel";
1 change: 1 addition & 0 deletions src/components/layout-elements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./Card";
export * from "./Divider";
export * from "./Flex";
export * from "./Grid";
export * from "./Dialog";
21 changes: 21 additions & 0 deletions src/stories/layout-elements/Dialog.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react";

import { Dialog } from "components";
import SimpleDialog from "./helpers/SimpleDialog";

const meta: Meta<typeof Dialog> = {
title: "UI/Layout/Dialog",
component: Dialog,
parameters: {
sourceLink:
"https://github.com/tremorlabs/tremor/tree/main/src/components/layout-elements/Dialog",
},
};

export default meta;
type Story = StoryObj<typeof Dialog>;

export const Default: Story = {
render: SimpleDialog,
parameters: {},
};
26 changes: 26 additions & 0 deletions src/stories/layout-elements/helpers/SimpleDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { useState } from "react";
import { Dialog, DialogPanel, Button, Title } from "components";

const SimpleDialog = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<div className="text-center">
<Button onClick={() => setIsOpen(true)}>Open Dialog</Button>
</div>
<Dialog open={isOpen} onClose={(val) => setIsOpen(val)} static={true}>
<DialogPanel>
<Title className="mb-3">Account Created Successfully</Title>
Your account has been created successfully. You can now login to your account. For more
information, please contact us.
<div className="mt-3">
<Button variant="light" onClick={() => setIsOpen(false)}>
Got it!
</Button>
</div>
</DialogPanel>
</Dialog>
</>
);
};
export default SimpleDialog;
19 changes: 19 additions & 0 deletions src/tests/layout-elements/Dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { act, render } from "@testing-library/react";

import { Dialog, DialogPanel } from "components";

describe("Dialog", () => {
test("renders the Dialog component", async () => {
const { findByText } = render(
<Dialog open onClose={() => {}}>
<DialogPanel>Test</DialogPanel>
</Dialog>,
);
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 300));
});
const message = await findByText("Test");
expect(message.textContent).toBe("Test");
});
});

0 comments on commit 939f635

Please sign in to comment.