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); } + } +}