diff --git a/demo/DemoApp.tsx b/demo/DemoApp.tsx
index cbe09c9..bc1c6d1 100644
--- a/demo/DemoApp.tsx
+++ b/demo/DemoApp.tsx
@@ -1,8 +1,8 @@
import React, {useState} from "react";
import "../index";
import {Checkbox} from "../src/Components/Forms/Checkbox";
-import { Radio } from "../src/Components/Forms/Radio";
-import {AirTrafficControl, Basket, FloppyDisk, House, TrashSimple, X, XCircle} from "@phosphor-icons/react";
+import {Radio} from "../src/Components/Forms/Radio";
+import {AirTrafficControl, Basket, FloppyDisk, House, TrashSimple, XCircle} from "@phosphor-icons/react";
import {Card} from "../src/Components/Card";
import {PasswordInput} from "../src/Components/Forms/PasswordInput";
import {RequiredField} from "../src/Components/Forms/RequiredField";
@@ -29,6 +29,9 @@ import {useCallableCurtain, useCurtains} from "../src/Components/Curtains/Curtai
import {Subapp, useCallableSubapp, useSubapps} from "../src/Components/Subapps/Subapps";
import {DemoSubapp} from "./DemoSubapp";
import {DemoCurtain} from "./DemoCurtain";
+import {DemoModal} from "./DemoModal";
+import {useCallableModal} from "../src/Components/Modals/Modals";
+import {ModalType} from "../src/Components/Modals/ModalsTypes";
export function DemoApp()
{
@@ -41,6 +44,9 @@ export function DemoApp()
// Easy subapp.
const easySubapp = useCallableSubapp();
+ // Easy modal.
+ const easyModal = useCallableModal((type: ModalType = ModalType.NONE) => );
+
const [datetime, setDatetime] = useState(null);
const [selected, setSelected] = useState(null);
@@ -489,7 +495,9 @@ export function DemoApp()
Modals
-
+
+
+
Notifications
diff --git a/demo/DemoModal.tsx b/demo/DemoModal.tsx
new file mode 100644
index 0000000..5df1b62
--- /dev/null
+++ b/demo/DemoModal.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import {Modal, useModal} from "../src/Components/Modals/Modals";
+import {ModalType} from "../src/Components/Modals/ModalsTypes";
+
+export function DemoModal({type}: { type: ModalType; })
+{
+ const {close} = useModal();
+
+ return (
+
+ Modal test content
+
+
+
+ );
+}
diff --git a/src/Components/Modals/Modals.tsx b/src/Components/Modals/Modals.tsx
new file mode 100644
index 0000000..e02c8fe
--- /dev/null
+++ b/src/Components/Modals/Modals.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import {X} from "@phosphor-icons/react";
+import {useCurtains, useCurtain, useCallableCurtain} from "../Curtains/Curtains";
+import {classes, Modify} from "../../Utils";
+import {ModalType, ModalTypeIcon} from "./ModalsTypes";
+
+/**
+ * More natural name of useCurtains for modals.
+ * @see useCurtains
+ */
+export const useModals = useCurtains;
+
+/**
+ * More natural name of useCurtain for modals.
+ * @see useCurtain
+ */
+export const useModal = useCurtain;
+
+/**
+ * More natural name of useCallableCurtain for modals.
+ * @see useCallableCurtain
+ */
+export const useCallableModal = useCallableCurtain;
+
+/**
+ * Modal main component.
+ */
+export function Modal({className, title, closable, type, children, ...props}: React.PropsWithChildren, {
+ /**
+ * Modal title.
+ */
+ title?: string;
+
+ /**
+ * Can disable close button.
+ */
+ closable?: boolean;
+
+ /**
+ * Modal type. None by default.
+ */
+ type?: ModalType;
+}>>)
+{
+ // Modal is closable by default.
+ closable = closable !== undefined ? closable : true;
+
+ // Modal type is NONE by default.
+ type = type !== undefined ? type : ModalType.NONE;
+
+ // Modal state.
+ const {close} = useModal();
+
+ return (
+
+
+ {ModalTypeIcon[type]} {title ?? ""}
+
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/Components/Modals/ModalsTypes.tsx b/src/Components/Modals/ModalsTypes.tsx
new file mode 100644
index 0000000..9f08822
--- /dev/null
+++ b/src/Components/Modals/ModalsTypes.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import {CheckCircle, Info, Record, Warning, WarningDiamond} from "@phosphor-icons/react";
+
+/**
+ * Modal types enumeration.
+ */
+export enum ModalType
+{
+ INFO = "info",
+ SUCCESS = "success",
+ WARNING = "warning",
+ ERROR = "error",
+ NONE = "none",
+}
+
+/**
+ * Icon for each modal type.
+ */
+export const ModalTypeIcon: Record = {
+ [ModalType.INFO]: ,
+ [ModalType.SUCCESS]: ,
+ [ModalType.WARNING]: ,
+ [ModalType.ERROR]: ,
+ [ModalType.NONE]: null,
+};
diff --git a/src/styles/_components.less b/src/styles/_components.less
index ed1675a..4b51633 100644
--- a/src/styles/_components.less
+++ b/src/styles/_components.less
@@ -11,6 +11,7 @@
@import "components/_list";
@import "components/_loaders";
@import "components/_menus";
+@import "components/_modal";
@import "components/_pagination";
@import "components/_select";
@import "components/_steps";
diff --git a/src/styles/components/_modal.less b/src/styles/components/_modal.less
new file mode 100644
index 0000000..621e5db
--- /dev/null
+++ b/src/styles/components/_modal.less
@@ -0,0 +1,99 @@
+.curtain > .modal
+{
+ position: absolute;
+
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ margin: auto;
+ width: 45em; max-width: 95%;
+ height: fit-content; max-height: 95%;
+ border-radius: 0.25em;
+ box-sizing: border-box;
+
+ background: var(--background);
+
+ overflow: auto;
+
+ > header
+ {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ > h1
+ {
+ flex: 1;
+ margin: auto;
+ padding: 1em 1em;
+
+ font-size: 1.33em;
+ font-weight: 650;
+
+ vertical-align: middle;
+
+ svg
+ {
+ margin-top: -0.2em;
+ margin-right: 0.1em;
+ vertical-align: middle;
+ }
+ }
+
+ > .close
+ {
+ transition: opacity 0.2s ease;
+
+ width: 2em;
+ height: 2em;
+ margin: auto 1em;
+ padding: 0;
+ border-radius: 2em;
+
+ box-shadow: 0 0 0 0 transparent;
+ outline: none;
+ border: none;
+ background: none;
+ color: var(--foreground-lightest);
+
+ opacity: 0.6;
+
+ &:hover
+ {
+ opacity: 1;
+ }
+
+ &:disabled
+ {
+ opacity: 0.33;
+ }
+ }
+ }
+
+ > main
+ {
+ padding: 1em;
+ }
+
+ &.info
+ {
+ > header > h1 { color: var(--primary); }
+ }
+
+ &.success
+ {
+ > header > h1 { color: var(--green); }
+ }
+
+ &.warning
+ {
+ > header > h1 { color: var(--orange); }
+ }
+
+ &.error
+ {
+ > header > h1 { color: var(--red); }
+ }
+}