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
				
			
		| 
						 | 
				
			
			@ -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
									
								
							
							
						
						
									
										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/_loaders";
 | 
			
		||||
@import "components/_menus";
 | 
			
		||||
@import "components/_modal";
 | 
			
		||||
@import "components/_pagination";
 | 
			
		||||
@import "components/_select";
 | 
			
		||||
@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…
	
	Add table
		
		Reference in a new issue