Add modal windows system and typed modals.
This commit is contained in:
parent
4a83e6a0fb
commit
5dee63db7b
6 changed files with 219 additions and 3 deletions
|
@ -2,7 +2,7 @@ import React, {useState} from "react";
|
||||||
import "../index";
|
import "../index";
|
||||||
import {Checkbox} from "../src/Components/Forms/Checkbox";
|
import {Checkbox} from "../src/Components/Forms/Checkbox";
|
||||||
import {Radio} from "../src/Components/Forms/Radio";
|
import {Radio} from "../src/Components/Forms/Radio";
|
||||||
import {AirTrafficControl, Basket, FloppyDisk, House, TrashSimple, X, XCircle} from "@phosphor-icons/react";
|
import {AirTrafficControl, Basket, FloppyDisk, House, TrashSimple, XCircle} from "@phosphor-icons/react";
|
||||||
import {Card} from "../src/Components/Card";
|
import {Card} from "../src/Components/Card";
|
||||||
import {PasswordInput} from "../src/Components/Forms/PasswordInput";
|
import {PasswordInput} from "../src/Components/Forms/PasswordInput";
|
||||||
import {RequiredField} from "../src/Components/Forms/RequiredField";
|
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 {Subapp, useCallableSubapp, useSubapps} from "../src/Components/Subapps/Subapps";
|
||||||
import {DemoSubapp} from "./DemoSubapp";
|
import {DemoSubapp} from "./DemoSubapp";
|
||||||
import {DemoCurtain} from "./DemoCurtain";
|
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()
|
export function DemoApp()
|
||||||
{
|
{
|
||||||
|
@ -41,6 +44,9 @@ export function DemoApp()
|
||||||
// Easy subapp.
|
// Easy subapp.
|
||||||
const easySubapp = useCallableSubapp(<DemoSubapp />);
|
const easySubapp = useCallableSubapp(<DemoSubapp />);
|
||||||
|
|
||||||
|
// Easy modal.
|
||||||
|
const easyModal = useCallableModal((type: ModalType = ModalType.NONE) => <DemoModal type={type} />);
|
||||||
|
|
||||||
const [datetime, setDatetime] = useState(null);
|
const [datetime, setDatetime] = useState(null);
|
||||||
|
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
|
@ -489,7 +495,9 @@ export function DemoApp()
|
||||||
<h3>Modals</h3>
|
<h3>Modals</h3>
|
||||||
|
|
||||||
<Card>
|
<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>
|
</Card>
|
||||||
|
|
||||||
<h2>Notifications</h2>
|
<h2>Notifications</h2>
|
||||||
|
|
16
demo/DemoModal.tsx
Normal file
16
demo/DemoModal.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
67
src/Components/Modals/Modals.tsx
Normal file
67
src/Components/Modals/Modals.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
25
src/Components/Modals/ModalsTypes.tsx
Normal file
25
src/Components/Modals/ModalsTypes.tsx
Normal 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,
|
||||||
|
};
|
|
@ -11,6 +11,7 @@
|
||||||
@import "components/_list";
|
@import "components/_list";
|
||||||
@import "components/_loaders";
|
@import "components/_loaders";
|
||||||
@import "components/_menus";
|
@import "components/_menus";
|
||||||
|
@import "components/_modal";
|
||||||
@import "components/_pagination";
|
@import "components/_pagination";
|
||||||
@import "components/_select";
|
@import "components/_select";
|
||||||
@import "components/_steps";
|
@import "components/_steps";
|
||||||
|
|
99
src/styles/components/_modal.less
Normal file
99
src/styles/components/_modal.less
Normal 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); }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue