Add modal windows system and typed modals.

This commit is contained in:
Madeorsk 2024-07-14 17:33:01 +02:00
parent 4a83e6a0fb
commit 5dee63db7b
Signed by: Madeorsk
SSH key fingerprint: SHA256:J9G0ofIOLKf7kyS2IfrMqtMaPdfsk1W02+oGueZzDDU
6 changed files with 219 additions and 3 deletions

View file

@ -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(<DemoSubapp />);
// Easy modal.
const easyModal = useCallableModal((type: ModalType = ModalType.NONE) => <DemoModal type={type} />);
const [datetime, setDatetime] = useState(null);
const [selected, setSelected] = useState(null);
@ -489,7 +495,9 @@ export function DemoApp()
<h3>Modals</h3>
<Card>
<button>Open a modal</button>
<button onClick={() => easyModal(ModalType.INFO)}>Open an info modal</button>
<button className={"warning"} onClick={() => easyModal(ModalType.WARNING)}>Open a warning modal</button>
<button className={"flat"} onClick={() => easyModal()}>Open a simple modal</button>
</Card>
<h2>Notifications</h2>

16
demo/DemoModal.tsx Normal file
View file

@ -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 type={type} title={"Modal title"}>
Modal test content
<button onClick={close}>OK</button>
</Modal>
);
}

View file

@ -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<Modify<React.HTMLAttributes<HTMLDivElement>, {
/**
* 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 (
<div className={classes("modal", type, className)} {...props}>
<header>
<h1>{ModalTypeIcon[type]} {title ?? ""}</h1>
<button className={"icon-only close"} onClick={close} disabled={!closable}><X size={20} /></button>
</header>
<main>
{children}
</main>
</div>
);
}

View file

@ -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, React.ReactElement> = {
[ModalType.INFO]: <Info size={24} weight={"duotone"} />,
[ModalType.SUCCESS]: <CheckCircle size={24} weight={"duotone"} />,
[ModalType.WARNING]: <Warning size={24} weight={"duotone"} />,
[ModalType.ERROR]: <WarningDiamond size={24} weight={"duotone"} />,
[ModalType.NONE]: null,
};

View file

@ -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";

View file

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