From e81bc9ac42a5e6d2df4abdb21031042dc94462c4 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 20 Sep 2024 15:34:24 +0200 Subject: [PATCH] Modal component (#20) * Add input name props * Rename classname attribute and add attributes for input component * Use value attribute for select * Remove unused import * Improve Sheets component * Add Modal component * Add displayActionButton and provide no button story --- src/components/Modal/Modal.tsx | 103 +++++++++++++++++++++++++++++++++ src/components/Modal/modal.css | 56 ++++++++++++++++++ src/index.ts | 1 + stories/Modal.stories.tsx | 97 +++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 src/components/Modal/Modal.tsx create mode 100644 src/components/Modal/modal.css create mode 100644 stories/Modal.stories.tsx diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx new file mode 100644 index 0000000..00fb810 --- /dev/null +++ b/src/components/Modal/Modal.tsx @@ -0,0 +1,103 @@ +import { ReactNode } from "react"; +import { Backdrop } from "../Backdrop/Backdrop"; +import { Button } from "../Button/Button"; +import { classnames } from "../utils/classnames"; +import "./modal.css"; + +type Props = { + open: boolean; + + /** + * Event triggered whenever the close button is clicked. + */ + onClose: () => void; + + /** + * If true the close button will be displayed + * Default: true + */ + displayCloseButton?: boolean; + + /** + * If true, the action button will be disabled. + * Default: false + */ + displayActionButton?: boolean; + + /** + * Event triggered whenever the action button is clicked. + * The action button should be considered the "primary" action. + */ + onAction?: () => void; + + /** + * Change the label of the close button. + * Default: Close + */ + labelCloseButton?: string; + + /** + * If true, the disable button will be disabled. + */ + disableCloseButton?: boolean; + + /** + * Change the label of the close button. + * Default: Action + */ + labelActionButton?: string; + + /** + * If true, the action button will be disabled. + */ + disableActionButton?: boolean; + + children: ReactNode; +}; + +export function Modal({ + open, + onClose, + disableActionButton, + disableCloseButton, + displayCloseButton = true, + displayActionButton = false, + labelActionButton = "Action", + labelCloseButton = "Close", + children, + onAction, +}: Props) { + return ( + <> + + +
+
{children}
+ +
+ {displayCloseButton && ( +
+
+ + ); +} diff --git a/src/components/Modal/modal.css b/src/components/Modal/modal.css new file mode 100644 index 0000000..dc38aee --- /dev/null +++ b/src/components/Modal/modal.css @@ -0,0 +1,56 @@ +.modal { + transition: transform 0.15s; + max-width: 800px; + overflow-y: auto; + overflow-x: hidden; + opacity: 0; + z-index: -1; + max-height: 100%; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale(0); + position: fixed; + display: flex; + flex-direction: column; + background-color: var(--codex-background); + padding: 1.5rem; + border-radius: var(--codex-border-radius); +} + +.modal--open { + transform: translate(-50%, -50%) scale(1); +} + +.modal--open { + opacity: 1; + z-index: 10; +} + +.modal-buttons--center { + margin-top: 1rem; + display: flex; + justify-content: center; +} + +.modal-buttons--between { + margin-top: 1rem; + display: flex; + justify-content: space-between; +} + +.modal-body { + flex: 1; +} + +.modal-title { + margin-left: auto; + margin-right: auto; + margin-bottom: 1rem; + font-size: 1.25rem; +} + +@media (min-width: 801px) { + .modal { + min-width: 500px; + } +} diff --git a/src/index.ts b/src/index.ts index 95742f6..a0f9eec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,3 +34,4 @@ export { Collapse } from "./components/Collapse/Collapse"; export { Placeholder } from "./components/Placeholder/Placeholder"; export { Sheets } from "./components/Sheets/Sheets"; export { Tabs } from "./components/Tabs/Tabs"; +export { Modal } from "./components/Modal/Modal"; diff --git a/stories/Modal.stories.tsx b/stories/Modal.stories.tsx new file mode 100644 index 0000000..e79760d --- /dev/null +++ b/stories/Modal.stories.tsx @@ -0,0 +1,97 @@ +import type { Meta } from "@storybook/react"; +import { useState } from "react"; +import { Modal } from "../src/components/Modal/Modal"; +import { fn } from "@storybook/test"; + +const meta = { + title: "Overlays/Modal", + component: Modal, + parameters: { + layout: "fullscreen", + inlineStories: false, + }, + tags: ["autodocs"], + argTypes: {}, + args: { + onAction: fn(), + onClose: fn(), + }, +} satisfies Meta; + +export default meta; + +type Props = { + onAction: () => void; + onClose: () => void; +}; + +const Template = (props: Props) => { + const [open, setOpen] = useState(false); + + const onOpen = () => setOpen(true); + + const onClose = () => { + props.onClose(); + setOpen(false); + }; + + return ( +
+ + +

Hello world

+
+
+ ); +}; + +export const Default = Template.bind({}); + +const ActionTemplate = (props: Props) => { + const [open, setOpen] = useState(false); + + const onOpen = () => setOpen(true); + + const onClose = () => { + props.onClose(); + setOpen(false); + }; + + const onAction = () => { + props.onAction(); + setOpen(false); + }; + + return ( +
+ + +

Hello world

+
+
+ ); +}; + +export const Action = ActionTemplate.bind({}); + +const NoButtonTemplate = (props: Props) => { + const [open, setOpen] = useState(false); + + const onOpen = () => setOpen(true); + + const onClose = () => { + props.onClose(); + setOpen(false); + }; + + return ( +
+ + +

Hello world

+
+
+ ); +}; + +export const NoButton = NoButtonTemplate.bind({});