Compare commits
	
		
			1 commit
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| af7f024af9 | 
					 120 changed files with 2445 additions and 7179 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -21,5 +21,7 @@ node_modules/
 | 
				
			||||||
.pnp.*
 | 
					.pnp.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Library
 | 
					# Library
 | 
				
			||||||
 | 
					 | 
				
			||||||
lib/
 | 
					lib/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*storybook.log
 | 
				
			||||||
 | 
					storybook-static
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								.prettierrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.prettierrc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						"useTabs": true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								.storybook/main.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.storybook/main.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import type { StorybookConfig } from '@storybook/react-vite';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const config: StorybookConfig = {
 | 
				
			||||||
 | 
					  "stories": [
 | 
				
			||||||
 | 
					    "../src/**/*.mdx",
 | 
				
			||||||
 | 
					    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "addons": [
 | 
				
			||||||
 | 
					    "@storybook/addon-docs"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "framework": {
 | 
				
			||||||
 | 
					    "name": "@storybook/react-vite",
 | 
				
			||||||
 | 
					    "options": {}
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export default config;
 | 
				
			||||||
							
								
								
									
										14
									
								
								.storybook/preview.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.storybook/preview.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import type { Preview } from '@storybook/react-vite'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const preview: Preview = {
 | 
				
			||||||
 | 
					  parameters: {
 | 
				
			||||||
 | 
					    controls: {
 | 
				
			||||||
 | 
					      matchers: {
 | 
				
			||||||
 | 
					       color: /(background|color)$/i,
 | 
				
			||||||
 | 
					       date: /Date$/i,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default preview;
 | 
				
			||||||
| 
						 | 
					@ -1,17 +0,0 @@
 | 
				
			||||||
steps:
 | 
					 | 
				
			||||||
  - name: build_library
 | 
					 | 
				
			||||||
    image: node:alpine
 | 
					 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - /tmp/woodpecker/cache/uikernel/core/node_modules:/woodpecker/src/code.zeptotech.net/UIKernel/Core/node_modules
 | 
					 | 
				
			||||||
      - /tmp/woodpecker/cache/uikernel/core/.yarn/cache:/woodpecker/src/code.zeptotech.net/UIKernel/Core/.yarn/cache
 | 
					 | 
				
			||||||
    secrets:
 | 
					 | 
				
			||||||
      - FORGE_TOKEN
 | 
					 | 
				
			||||||
    commands:
 | 
					 | 
				
			||||||
      - corepack enable
 | 
					 | 
				
			||||||
      - yarn install
 | 
					 | 
				
			||||||
      - yarn build
 | 
					 | 
				
			||||||
      - ./.woodpecker/yarn_auth.sh
 | 
					 | 
				
			||||||
      - yarn npm publish
 | 
					 | 
				
			||||||
    when:
 | 
					 | 
				
			||||||
      - event: tag
 | 
					 | 
				
			||||||
        ref: refs/tags/v*
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
#!/bin/sh
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echo "npmRegistries:
 | 
					 | 
				
			||||||
  //code.zeptotech.net/api/packages/UIKernel/npm/:
 | 
					 | 
				
			||||||
    npmAlwaysAuth: true
 | 
					 | 
				
			||||||
    npmAuthToken: \"$FORGE_TOKEN\"" >> ./.yarnrc.yml
 | 
					 | 
				
			||||||
							
								
								
									
										635
									
								
								demo/DemoApp.tsx
									
										
									
									
									
								
							
							
						
						
									
										635
									
								
								demo/DemoApp.tsx
									
										
									
									
									
								
							| 
						 | 
					@ -1,635 +0,0 @@
 | 
				
			||||||
import React, {useState} from "react";
 | 
					 | 
				
			||||||
import {Checkbox} from "../src/Components/Forms/Checkbox";
 | 
					 | 
				
			||||||
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";
 | 
					 | 
				
			||||||
import {Float} from "../src/Components/Floating/Float";
 | 
					 | 
				
			||||||
import {Tooltip} from "../src/Components/Floating/Tooltip";
 | 
					 | 
				
			||||||
import {DatepickerInput} from "../src/Components/Forms/DatepickerInput";
 | 
					 | 
				
			||||||
import {TimepickerInput} from "../src/Components/Forms/TimepickerInput";
 | 
					 | 
				
			||||||
import {Select} from "../src/Components/Select/Select";
 | 
					 | 
				
			||||||
import {SpinningLoader} from "../src/Components/Loaders/SpinningLoader";
 | 
					 | 
				
			||||||
import {ListLoader} from "../src/Components/Loaders/ListLoader";
 | 
					 | 
				
			||||||
import {GenericLoader} from "../src/Components/Loaders/GenericLoader";
 | 
					 | 
				
			||||||
import {MainMenu} from "../src/Components/Menus/MainMenu";
 | 
					 | 
				
			||||||
import {SubmenuFloat} from "../src/Components/Menus/SubmenuFloat";
 | 
					 | 
				
			||||||
import {Submenu} from "../src/Components/Menus/Submenu";
 | 
					 | 
				
			||||||
import {SubmenuItem, SubmenuItemSubmenu} from "../src/Components/Menus/SubmenuItem";
 | 
					 | 
				
			||||||
import {MainMenuItemSubmenu, MainMenuLink} from "../src/Components/Menus/MainMenuItem";
 | 
					 | 
				
			||||||
import {AppItem, AppLink, AppsMenu} from "../src/Components/Menus/AppsMenu";
 | 
					 | 
				
			||||||
import {Application} from "../src/Application/Application";
 | 
					 | 
				
			||||||
import {Outlet} from "react-router-dom";
 | 
					 | 
				
			||||||
import {ToggleSwitch} from "../src/Components/Forms/ToggleSwitch";
 | 
					 | 
				
			||||||
import {Step, Steps} from "../src/Components/Steps/Steps";
 | 
					 | 
				
			||||||
import {AsyncPaginate, AutoPaginate, Paginate} from "../src/Components/Pagination/Paginate";
 | 
					 | 
				
			||||||
import {useCallableCurtain, useCurtains} from "../src/Components/Curtains/Curtains";
 | 
					 | 
				
			||||||
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";
 | 
					 | 
				
			||||||
import {Buttons} from "../src/Components/Buttons/Buttons";
 | 
					 | 
				
			||||||
import {useNotify} from "../src/Components/Notifications/Notifications";
 | 
					 | 
				
			||||||
import {Notification, NotificationType} from "../src/Components/Notifications/Notification";
 | 
					 | 
				
			||||||
import {Box} from "../src/Components/Box";
 | 
					 | 
				
			||||||
import {Await, useAsync} from "../src/Async";
 | 
					 | 
				
			||||||
import {NotifyErrorsBoundary} from "../src/Components/Errors/NotifyErrorsBoundary";
 | 
					 | 
				
			||||||
import {DemoFailingComponent, DemoResetComponent} from "./DemoFailingComponent";
 | 
					 | 
				
			||||||
import {Tip} from "../src/Components/Tips/Tip";
 | 
					 | 
				
			||||||
import {useKernelContext} from "../src/KernelGlobalContext";
 | 
					 | 
				
			||||||
import {KernelContext} from "./DemoKernelContext";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function DemoApp()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const curtains = useCurtains();
 | 
					 | 
				
			||||||
	const subapps = useSubapps();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Easy curtain.
 | 
					 | 
				
			||||||
	const easyCurtain = useCallableCurtain(<DemoCurtain />);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Easy subapp.
 | 
					 | 
				
			||||||
	const easySubapp = useCallableSubapp(<DemoSubapp />);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Easy modal.
 | 
					 | 
				
			||||||
	const easyModal = useCallableModal((type: ModalType = ModalType.NONE) => <DemoModal type={type} />);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const notify = useNotify();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [datetime, setDatetime] = useState(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [selected, setSelected] = useState(null);
 | 
					 | 
				
			||||||
	const [anotherSelected, setAnotherSelected] = useState(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [page, setPage] = useState(11);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [asyncChange, setAsyncChange] = useState(0);
 | 
					 | 
				
			||||||
	const [anotherChange, setAnotherChange] = useState(0);
 | 
					 | 
				
			||||||
	const [asyncData] = useAsync<string>(() => new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
		setTimeout(() => {
 | 
					 | 
				
			||||||
			resolve("async data (" + Math.random() + ")");
 | 
					 | 
				
			||||||
		}, 2000);
 | 
					 | 
				
			||||||
	}), [asyncChange]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [failingComponentsCount, setFailingComponentsCount] = useState(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [kernelContext, setKernelContext] = useKernelContext(KernelContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Application>
 | 
					 | 
				
			||||||
			<MainMenu>
 | 
					 | 
				
			||||||
				<MainMenuLink to={"/"}><House /> Home</MainMenuLink>
 | 
					 | 
				
			||||||
				<MainMenuLink to={"/test"}><AirTrafficControl /> Test</MainMenuLink>
 | 
					 | 
				
			||||||
				<MainMenuItemSubmenu submenu={
 | 
					 | 
				
			||||||
					<Submenu>
 | 
					 | 
				
			||||||
						<SubmenuItem>Test 1</SubmenuItem>
 | 
					 | 
				
			||||||
						<SubmenuItem>Test 2</SubmenuItem>
 | 
					 | 
				
			||||||
						<SubmenuItemSubmenu submenu={
 | 
					 | 
				
			||||||
							<Submenu>
 | 
					 | 
				
			||||||
								<SubmenuItem>Test A</SubmenuItem>
 | 
					 | 
				
			||||||
								<SubmenuItem>Test B</SubmenuItem>
 | 
					 | 
				
			||||||
								<SubmenuItemSubmenu submenu={
 | 
					 | 
				
			||||||
									<Submenu>
 | 
					 | 
				
			||||||
										<SubmenuItem href={"#first-last-choice"}>First last choice</SubmenuItem>
 | 
					 | 
				
			||||||
										<SubmenuItem>Another last choice</SubmenuItem>
 | 
					 | 
				
			||||||
									</Submenu>
 | 
					 | 
				
			||||||
								}><Basket /> Submenu in submenu</SubmenuItemSubmenu>
 | 
					 | 
				
			||||||
							</Submenu>
 | 
					 | 
				
			||||||
						}><Basket /> Submenu</SubmenuItemSubmenu>
 | 
					 | 
				
			||||||
					</Submenu>
 | 
					 | 
				
			||||||
				}>
 | 
					 | 
				
			||||||
					<Basket /> Submenu
 | 
					 | 
				
			||||||
				</MainMenuItemSubmenu>
 | 
					 | 
				
			||||||
			</MainMenu>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h1>KernelUI</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Headings</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Box>
 | 
					 | 
				
			||||||
				<h1>Demo app</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<h2>Second title</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<h3>Third title</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<h4>Fourth title</h4>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<h5>Fifth title</h5>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<h6>Sixth title</h6>
 | 
					 | 
				
			||||||
			</Box>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Buttons</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<button type={"button"}>A cool button</button>
 | 
					 | 
				
			||||||
			<a className={"button"} href={"#"}>A link button</a>
 | 
					 | 
				
			||||||
			<button type={"button"} className={"flat"}>A flat button</button>
 | 
					 | 
				
			||||||
			<button type={"button"} className={"validation"}><FloppyDisk /> A validation button</button>
 | 
					 | 
				
			||||||
			<button type={"button"} className={"cancel"}><XCircle /> A cancellation button</button>
 | 
					 | 
				
			||||||
			<button type={"button"} className={"delete"}><TrashSimple /> A deletion button</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Forms</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<form>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					Text label <RequiredField/>
 | 
					 | 
				
			||||||
					<input type={"text"} placeholder={"Normal demo text"} required={true}/>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					Textarea label <RequiredField/>
 | 
					 | 
				
			||||||
					<textarea placeholder={"A normal textarea."} required={true}></textarea>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<PasswordInput>
 | 
					 | 
				
			||||||
					Test password
 | 
					 | 
				
			||||||
				</PasswordInput>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					Disabled input
 | 
					 | 
				
			||||||
					<input type={"text"} name={"disabled"} value={"fixed value"} disabled={true} />
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<DatepickerInput value={datetime} onChange={setDatetime}>
 | 
					 | 
				
			||||||
					Date test
 | 
					 | 
				
			||||||
				</DatepickerInput>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<TimepickerInput value={datetime} onChange={setDatetime}>
 | 
					 | 
				
			||||||
					Time test
 | 
					 | 
				
			||||||
				</TimepickerInput>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<p>Currently selected datetime: <strong>{datetime ? datetime.toISOString() : "none"}</strong></p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Checkbox>Checkbox demo</Checkbox>
 | 
					 | 
				
			||||||
				<ToggleSwitch>Toggle switch demo</ToggleSwitch>
 | 
					 | 
				
			||||||
				<Radio name={"radio-test"}>Radio box test</Radio>
 | 
					 | 
				
			||||||
				<Radio name={"radio-test"}>Radio box test</Radio>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Tip>
 | 
					 | 
				
			||||||
					A tip component, very useful in forms which require some explanations.
 | 
					 | 
				
			||||||
				</Tip>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Select options={{
 | 
					 | 
				
			||||||
					"a": "AAAAAA",
 | 
					 | 
				
			||||||
					"b": "BBBBBB",
 | 
					 | 
				
			||||||
					"c": "CCCCCC",
 | 
					 | 
				
			||||||
					"d": "DDDDDD",
 | 
					 | 
				
			||||||
					"e": "EEEEEE",
 | 
					 | 
				
			||||||
					"f": "FFFFFF",
 | 
					 | 
				
			||||||
					"g": "GGGGGG",
 | 
					 | 
				
			||||||
					"h": "HHHHHH",
 | 
					 | 
				
			||||||
					"i": "IIIIII",
 | 
					 | 
				
			||||||
					"j": "JJJJJJ",
 | 
					 | 
				
			||||||
					"k": "KKKKKK",
 | 
					 | 
				
			||||||
					"l": "LLLLLL",
 | 
					 | 
				
			||||||
					"m": "MMMMMM",
 | 
					 | 
				
			||||||
					"n": "NNNNNN",
 | 
					 | 
				
			||||||
				}} value={selected} onChange={setSelected} selectibleMaxCount={3} placeholder={"Simple test"}>
 | 
					 | 
				
			||||||
					Simple select test
 | 
					 | 
				
			||||||
				</Select>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Select value={anotherSelected} onChange={setAnotherSelected} options={{
 | 
					 | 
				
			||||||
					"test": "test",
 | 
					 | 
				
			||||||
					"foo": "foo",
 | 
					 | 
				
			||||||
					"bar": "bar",
 | 
					 | 
				
			||||||
				}} required={true}>
 | 
					 | 
				
			||||||
					At least one selected element
 | 
					 | 
				
			||||||
				</Select>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Buttons placement={"center"}>
 | 
					 | 
				
			||||||
					<button>Validation test</button>
 | 
					 | 
				
			||||||
				</Buttons>
 | 
					 | 
				
			||||||
			</form>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>HTML</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<div>
 | 
					 | 
				
			||||||
				<a href={"#"}>Link test</a>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			<p>
 | 
					 | 
				
			||||||
				<strong>Lorem ipsum</strong> dolor <code>sit amet</code>, <em>consectetur</em> adipiscing elit <a
 | 
					 | 
				
			||||||
				href={"https://aleph.land"} target={"_blank"}>aleph</a>. Donec accumsan
 | 
					 | 
				
			||||||
				pulvinar felis, vitae eleifend augue lacinia tempus. Integer nec iaculis ante. Duis a quam urna. Nullam
 | 
					 | 
				
			||||||
				tincidunt rutrum felis, a efficitur enim facilisis sit amet. Quisque dictum semper sagittis. Maecenas in orci
 | 
					 | 
				
			||||||
				hendrerit, tempor nunc non, tempus mi. Praesent blandit varius rutrum. Nullam quis mauris eros. Vestibulum
 | 
					 | 
				
			||||||
				commodo libero sed pellentesque pharetra. Donec eget fringilla ante. Aliquam id leo massa. Duis dictum nunc ut
 | 
					 | 
				
			||||||
				dolor iaculis malesuada. Nulla elementum justo a sem eleifend finibus. Phasellus bibendum elit nibh, at tempor
 | 
					 | 
				
			||||||
				odio efficitur id.
 | 
					 | 
				
			||||||
			</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Steps</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<div className={"steps-counter"}>
 | 
					 | 
				
			||||||
				<h3 className={"step"}>Step one</h3>
 | 
					 | 
				
			||||||
				<h3 className={"step"}>Step two</h3>
 | 
					 | 
				
			||||||
				<h3 className={"step"}>Step three</h3>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Lists</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<ul>
 | 
					 | 
				
			||||||
				<li>One</li>
 | 
					 | 
				
			||||||
				<li>Two</li>
 | 
					 | 
				
			||||||
				<li>Three</li>
 | 
					 | 
				
			||||||
				<li>Four</li>
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<ol>
 | 
					 | 
				
			||||||
				<li>One</li>
 | 
					 | 
				
			||||||
				<li>Two</li>
 | 
					 | 
				
			||||||
				<li>Three</li>
 | 
					 | 
				
			||||||
				<li>Four</li>
 | 
					 | 
				
			||||||
			</ol>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Tables</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<table>
 | 
					 | 
				
			||||||
				<thead>
 | 
					 | 
				
			||||||
				<tr>
 | 
					 | 
				
			||||||
					<th>Column 1</th>
 | 
					 | 
				
			||||||
					<th>Column 2</th>
 | 
					 | 
				
			||||||
					<th colSpan={2}>Column 3</th>
 | 
					 | 
				
			||||||
				</tr>
 | 
					 | 
				
			||||||
				<tr>
 | 
					 | 
				
			||||||
					<th>A</th>
 | 
					 | 
				
			||||||
					<th>B</th>
 | 
					 | 
				
			||||||
					<th>C</th>
 | 
					 | 
				
			||||||
					<th>D</th>
 | 
					 | 
				
			||||||
				</tr>
 | 
					 | 
				
			||||||
				</thead>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<tbody>
 | 
					 | 
				
			||||||
				<tr>
 | 
					 | 
				
			||||||
					<td>Lorem</td>
 | 
					 | 
				
			||||||
					<td>Ipsum</td>
 | 
					 | 
				
			||||||
					<td>Dolor</td>
 | 
					 | 
				
			||||||
					<td>Amet</td>
 | 
					 | 
				
			||||||
				</tr>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<tr>
 | 
					 | 
				
			||||||
					<td>Foo</td>
 | 
					 | 
				
			||||||
					<td>Bar</td>
 | 
					 | 
				
			||||||
					<td>Baz</td>
 | 
					 | 
				
			||||||
					<td>John</td>
 | 
					 | 
				
			||||||
				</tr>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<tr>
 | 
					 | 
				
			||||||
					<td>Alice</td>
 | 
					 | 
				
			||||||
					<td>Bob</td>
 | 
					 | 
				
			||||||
					<td></td>
 | 
					 | 
				
			||||||
					<td>Jack</td>
 | 
					 | 
				
			||||||
				</tr>
 | 
					 | 
				
			||||||
				</tbody>
 | 
					 | 
				
			||||||
			</table>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Cards</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<h3>Card title</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<p>
 | 
					 | 
				
			||||||
					Donec lobortis quam sapien, et efficitur dolor laoreet ut. Ut pretium, lacus at bibendum rutrum, nibh nibh
 | 
					 | 
				
			||||||
					scelerisque nisi, nec semper dolor turpis sed tortor. Nulla massa sapien, accumsan id vestibulum et, elementum
 | 
					 | 
				
			||||||
					eget metus. Morbi quis bibendum purus. Nunc at fermentum tortor. Quisque viverra diam in sem auctor blandit.
 | 
					 | 
				
			||||||
					Vestibulum dignissim bibendum nunc, non tristique quam sollicitudin eu. Ut feugiat lectus tellus, tempus
 | 
					 | 
				
			||||||
					viverra sapien aliquet vel. Morbi ac est mauris. Praesent facilisis ut tellus at cursus. Aenean placerat nulla
 | 
					 | 
				
			||||||
					non mi vulputate hendrerit. Praesent fermentum dui eu gravida pharetra. Quisque rhoncus, magna non congue
 | 
					 | 
				
			||||||
					egestas, leo lorem malesuada felis, eu imperdiet orci magna ultrices est.
 | 
					 | 
				
			||||||
				</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Buttons>
 | 
					 | 
				
			||||||
					<button type={"button"}>Button position ?</button>
 | 
					 | 
				
			||||||
					<button type={"button"}>Button position ?</button>
 | 
					 | 
				
			||||||
				</Buttons>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<p>Another small card</p>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Popovers</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Float mode={"hover"} content={"Do you see me?"}>
 | 
					 | 
				
			||||||
				<button type={"button"}>Hover me!</button>
 | 
					 | 
				
			||||||
			</Float>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Float mode={"focus"} content={<>I am <strong>focused</strong></>}>
 | 
					 | 
				
			||||||
				<button>Focus me!</button>
 | 
					 | 
				
			||||||
			</Float>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Float mode={"click"} content={(
 | 
					 | 
				
			||||||
				<div>
 | 
					 | 
				
			||||||
					You can add complex (clickable) content in me.
 | 
					 | 
				
			||||||
					<button type={"button"}>OK</button>
 | 
					 | 
				
			||||||
				</div>
 | 
					 | 
				
			||||||
			)}>
 | 
					 | 
				
			||||||
				<button>Click me!</button>
 | 
					 | 
				
			||||||
			</Float>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Float mode={"always"} content={"I am always shown."} floatingOptions={{placement: "top"}}>
 | 
					 | 
				
			||||||
				<button>Why always me?</button>
 | 
					 | 
				
			||||||
			</Float>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<div>
 | 
					 | 
				
			||||||
				<Float mode={"managed"} content={(show, hide) => (<button onClick={hide}>I can hide the popover!</button>)}>
 | 
					 | 
				
			||||||
					{(show, hide) => (
 | 
					 | 
				
			||||||
						<button type={"button"} onClick={show}>Customized behavior</button>
 | 
					 | 
				
			||||||
					)}
 | 
					 | 
				
			||||||
				</Float>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Tooltips</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				This is a very simple <Tooltip content={"I am a beautiful simple tooltip."}><a>tooltip</a></Tooltip>.
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Loaders</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Simple loaders</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<SpinningLoader inline={true} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<ListLoader/>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Generic loader</h3>
 | 
					 | 
				
			||||||
			<GenericLoader>
 | 
					 | 
				
			||||||
				<Card>
 | 
					 | 
				
			||||||
					Sample content.
 | 
					 | 
				
			||||||
				</Card>
 | 
					 | 
				
			||||||
			</GenericLoader>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Menus</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<SubmenuFloat submenu={
 | 
					 | 
				
			||||||
				<Submenu>
 | 
					 | 
				
			||||||
					<SubmenuItem>Test 1</SubmenuItem>
 | 
					 | 
				
			||||||
					<SubmenuItem>Test 2</SubmenuItem>
 | 
					 | 
				
			||||||
					<SubmenuItemSubmenu submenu={
 | 
					 | 
				
			||||||
						<Submenu>
 | 
					 | 
				
			||||||
							<SubmenuItem>Test A</SubmenuItem>
 | 
					 | 
				
			||||||
							<SubmenuItem><AirTrafficControl /> Test B</SubmenuItem>
 | 
					 | 
				
			||||||
						</Submenu>
 | 
					 | 
				
			||||||
					}>
 | 
					 | 
				
			||||||
						<Basket /> Submenu
 | 
					 | 
				
			||||||
					</SubmenuItemSubmenu>
 | 
					 | 
				
			||||||
				</Submenu>
 | 
					 | 
				
			||||||
			} floatingOptions={{placement: "right-start"}}>
 | 
					 | 
				
			||||||
				<button>Submenu on a button</button>
 | 
					 | 
				
			||||||
			</SubmenuFloat>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>App selectors</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<AppsMenu>
 | 
					 | 
				
			||||||
				<AppLink to={"/"}>
 | 
					 | 
				
			||||||
					<House />
 | 
					 | 
				
			||||||
					Home
 | 
					 | 
				
			||||||
				</AppLink>
 | 
					 | 
				
			||||||
				<AppLink to={"/test"}>
 | 
					 | 
				
			||||||
					<AirTrafficControl />
 | 
					 | 
				
			||||||
					Test link
 | 
					 | 
				
			||||||
				</AppLink>
 | 
					 | 
				
			||||||
				<AppItem>
 | 
					 | 
				
			||||||
					<Basket />
 | 
					 | 
				
			||||||
					Test 3
 | 
					 | 
				
			||||||
				</AppItem>
 | 
					 | 
				
			||||||
			</AppsMenu>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Outlet />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>App steps</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Basic</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Steps>
 | 
					 | 
				
			||||||
				<Step stepKey={"abc"}>
 | 
					 | 
				
			||||||
					<Card>
 | 
					 | 
				
			||||||
						<h4>First step</h4>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						ABC STEP
 | 
					 | 
				
			||||||
					</Card>
 | 
					 | 
				
			||||||
				</Step>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Step stepKey={"def"} stepTitle={"Title"}>
 | 
					 | 
				
			||||||
					<Card>
 | 
					 | 
				
			||||||
						<h4>Big content</h4>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						<p>
 | 
					 | 
				
			||||||
							Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam id dignissim ligula, ut tempus sem. Sed
 | 
					 | 
				
			||||||
							faucibus tincidunt ante vel iaculis. Duis hendrerit, orci eu gravida interdum, nulla lacus congue augue,
 | 
					 | 
				
			||||||
							nec efficitur diam dui sollicitudin eros. Donec lacus lectus, aliquam nec feugiat non, gravida ac est.
 | 
					 | 
				
			||||||
							Suspendisse feugiat justo quis dui vehicula, sed auctor est mollis. Sed hendrerit nisi non lectus lacinia,
 | 
					 | 
				
			||||||
							id posuere dolor dignissim. Quisque commodo mi sit amet quam tincidunt auctor. Ut sit amet scelerisque
 | 
					 | 
				
			||||||
							sem. Nulla rhoncus, orci vitae cursus ullamcorper, ex odio rhoncus enim, et blandit elit libero quis est.
 | 
					 | 
				
			||||||
							Suspendisse lectus nunc, gravida sit amet vulputate eget, porta ac odio.
 | 
					 | 
				
			||||||
						</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						<p>
 | 
					 | 
				
			||||||
							Mauris egestas bibendum facilisis. Maecenas accumsan lorem arcu, ut faucibus dui euismod et. Nulla et
 | 
					 | 
				
			||||||
							dignissim est, interdum luctus diam. Aliquam condimentum ex augue, id porttitor enim vestibulum quis.
 | 
					 | 
				
			||||||
							Vivamus sed convallis leo. Duis finibus, ipsum sed condimentum viverra, ipsum sapien congue nunc, sed
 | 
					 | 
				
			||||||
							fermentum metus ante quis ligula. Fusce eleifend ante in leo molestie, at suscipit metus cursus. Class
 | 
					 | 
				
			||||||
							aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed laoreet justo ac
 | 
					 | 
				
			||||||
							lacus porta, sed ultricies est mattis. Integer finibus purus metus, quis posuere risus suscipit suscipit.
 | 
					 | 
				
			||||||
							Nulla facilisi. Duis tincidunt vitae enim eu sagittis.
 | 
					 | 
				
			||||||
						</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						<p>
 | 
					 | 
				
			||||||
							Donec rutrum tellus vitae vehicula tempor. Sed porta tempus leo, vel aliquam risus scelerisque nec.
 | 
					 | 
				
			||||||
							Pellentesque diam nibh, ultrices in rhoncus ut, rhoncus et ligula. Duis pellentesque diam purus, ut
 | 
					 | 
				
			||||||
							scelerisque turpis condimentum sit amet. Sed sit amet efficitur tortor, vitae aliquet quam. Nullam
 | 
					 | 
				
			||||||
							placerat dui eu sapien condimentum, placerat convallis sapien imperdiet. Suspendisse vitae laoreet ex.
 | 
					 | 
				
			||||||
							Etiam quis rhoncus ante. Ut egestas eget ipsum ultrices tempus. Vivamus non odio non nisl aliquet rhoncus.
 | 
					 | 
				
			||||||
							Ut et nisl placerat, interdum turpis a, condimentum mauris. In laoreet lobortis justo. Maecenas vehicula
 | 
					 | 
				
			||||||
							magna non libero posuere rutrum. Praesent eget lectus feugiat dui pellentesque vehicula a sed felis.
 | 
					 | 
				
			||||||
							Curabitur nunc orci, vehicula non gravida sed, suscipit sed diam. Nam semper, dui eu volutpat vulputate,
 | 
					 | 
				
			||||||
							metus mauris congue lectus, ultrices sollicitudin eros felis ac erat.
 | 
					 | 
				
			||||||
						</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						<p>
 | 
					 | 
				
			||||||
							Proin et rhoncus purus. Etiam nulla libero, dictum sed quam lacinia, consequat euismod ipsum. Donec quis
 | 
					 | 
				
			||||||
							tristique metus. Cras vitae pretium massa. Etiam laoreet, eros in rhoncus ultrices, nibh nibh bibendum
 | 
					 | 
				
			||||||
							diam, nec ultricies mi ante non ex. Pellentesque eget mattis dolor, eget pulvinar lorem. Nullam ante
 | 
					 | 
				
			||||||
							dolor, ultricies et malesuada in, efficitur sit amet diam. Duis ligula augue, vestibulum sit amet ligula
 | 
					 | 
				
			||||||
							ac, vestibulum tincidunt eros. Nullam commodo euismod vulputate. Morbi varius accumsan diam eu
 | 
					 | 
				
			||||||
							pellentesque. Nunc vehicula pretium risus dapibus cursus. Mauris sit amet est at ipsum scelerisque
 | 
					 | 
				
			||||||
							lobortis. Aenean eget quam sit amet arcu mattis interdum in at neque.
 | 
					 | 
				
			||||||
						</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						<p>
 | 
					 | 
				
			||||||
							Mauris efficitur, enim pellentesque maximus faucibus, nibh enim gravida urna, quis dignissim turpis velit
 | 
					 | 
				
			||||||
							quis nibh. Mauris eget vestibulum tellus. Duis mollis, ante in egestas lacinia, felis massa rutrum ante,
 | 
					 | 
				
			||||||
							vel placerat lectus neque in purus. Fusce sodales nunc vel ligula mollis tincidunt. Sed ac viverra ligula.
 | 
					 | 
				
			||||||
							Vestibulum ut velit sit amet ipsum cursus posuere in nec lacus. Praesent vel odio pellentesque,
 | 
					 | 
				
			||||||
							ullamcorper metus in, accumsan dolor. Donec vel mi ultrices, interdum arcu vel, pellentesque nunc.
 | 
					 | 
				
			||||||
						</p>
 | 
					 | 
				
			||||||
					</Card>
 | 
					 | 
				
			||||||
				</Step>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Step stepKey={"ghi"}>
 | 
					 | 
				
			||||||
					<Card>
 | 
					 | 
				
			||||||
						<h4>Third step</h4>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						GHI STEP
 | 
					 | 
				
			||||||
					</Card>
 | 
					 | 
				
			||||||
				</Step>
 | 
					 | 
				
			||||||
			</Steps>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Pagination</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Normal pagination</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Paginate onChange={setPage} count={72} page={page}>
 | 
					 | 
				
			||||||
				<Card>Page {page}</Card>
 | 
					 | 
				
			||||||
			</Paginate>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Auto pagination</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<AutoPaginate count={55}>
 | 
					 | 
				
			||||||
				{(page) => (
 | 
					 | 
				
			||||||
					<Card>Page {page}</Card>
 | 
					 | 
				
			||||||
				)}
 | 
					 | 
				
			||||||
			</AutoPaginate>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Async pagination</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<AsyncPaginate count={async () => { return 72; }} getData={async () => (["a", Math.random(), "c"])}>
 | 
					 | 
				
			||||||
				{(data) => (
 | 
					 | 
				
			||||||
					<>
 | 
					 | 
				
			||||||
						{
 | 
					 | 
				
			||||||
							data.map((value, index) => (
 | 
					 | 
				
			||||||
								<div key={index}>{value}</div>
 | 
					 | 
				
			||||||
							))
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					</>
 | 
					 | 
				
			||||||
				)}
 | 
					 | 
				
			||||||
			</AsyncPaginate>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Curtains & co</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Curtains</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<button onClick={() => {
 | 
					 | 
				
			||||||
					curtains.open(<DemoCurtain />);
 | 
					 | 
				
			||||||
				}}>Open a curtain</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<button onClick={easyCurtain}>Easy with callable curtain</button>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Subapps</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<button onClick={() => {
 | 
					 | 
				
			||||||
					subapps.open(
 | 
					 | 
				
			||||||
						<Subapp title={"Title test"}>
 | 
					 | 
				
			||||||
							<p>A test content.</p>
 | 
					 | 
				
			||||||
							<Buttons placement={"center"}>
 | 
					 | 
				
			||||||
								<Float mode={"click"} content={"Test content."}>
 | 
					 | 
				
			||||||
									<button>A button with floating content</button>
 | 
					 | 
				
			||||||
								</Float>
 | 
					 | 
				
			||||||
							</Buttons>
 | 
					 | 
				
			||||||
						</Subapp>
 | 
					 | 
				
			||||||
					)
 | 
					 | 
				
			||||||
				}}>Open a subapp
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<button onClick={() => { subapps.open(<DemoSubapp />) }}>
 | 
					 | 
				
			||||||
					Complex subapp with component
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<button onClick={easySubapp}>
 | 
					 | 
				
			||||||
					Easy with callable subapp
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<button onClick={() => {
 | 
					 | 
				
			||||||
					subapps.open(
 | 
					 | 
				
			||||||
						<Subapp title={"Very long subapp"}>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
							<Card><p>A test content.</p></Card>
 | 
					 | 
				
			||||||
						</Subapp>
 | 
					 | 
				
			||||||
					)
 | 
					 | 
				
			||||||
				}}>A long subapp
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h3>Modals</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<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>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<button onClick={() => { notify(<Notification type={NotificationType.INFO}>Test notification</Notification>); }}>Information notification</button>
 | 
					 | 
				
			||||||
				<button className={"success"} onClick={() => { notify(<Notification type={NotificationType.SUCCESS}>Test notification</Notification>); }}>Success notification</button>
 | 
					 | 
				
			||||||
				<button className={"warning"} onClick={() => { notify(<Notification type={NotificationType.WARNING}>Test notification</Notification>); }}>Warning notification</button>
 | 
					 | 
				
			||||||
				<button className={"error"} onClick={() => { notify(<Notification type={NotificationType.ERROR}>Test notification</Notification>); }}>Error notification</button>
 | 
					 | 
				
			||||||
				<button className={"flat"} onClick={() => { notify(<Notification>Test notification</Notification>); }}>Generic notification</button>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Async</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				Async data test:
 | 
					 | 
				
			||||||
				<Await async={asyncData} fallback={<SpinningLoader />}>
 | 
					 | 
				
			||||||
					{(data) => (
 | 
					 | 
				
			||||||
						<p>Data: {data}</p>
 | 
					 | 
				
			||||||
					)}
 | 
					 | 
				
			||||||
				</Await>
 | 
					 | 
				
			||||||
				<button type={"button"} onClick={() => setAsyncChange(asyncChange + 1)}>Change async deps</button>
 | 
					 | 
				
			||||||
				<button type={"button"} onClick={() => setAnotherChange(anotherChange + 1)}>Change something else</button>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>Error boundaries</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<button type={"button"} className={"error"}
 | 
					 | 
				
			||||||
				        onClick={() => setFailingComponentsCount(failingComponentsCount + 1)}>Do something dangerous</button>
 | 
					 | 
				
			||||||
				<NotifyErrorsBoundary fallback={<DemoResetComponent />}>
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						[...Array(failingComponentsCount)].map(() => <DemoFailingComponent />)
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				</NotifyErrorsBoundary>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<h2>
 | 
					 | 
				
			||||||
				Global states
 | 
					 | 
				
			||||||
			</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					Kernel context data
 | 
					 | 
				
			||||||
					<input type={"text"} name={"kernel-context-data"}
 | 
					 | 
				
			||||||
					       value={kernelContext}
 | 
					 | 
				
			||||||
					       onChange={(event) => setKernelContext(event.currentTarget.value)} />
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<Buttons placement={"center"}>
 | 
					 | 
				
			||||||
					<button type={"button"} onClick={easySubapp}>Open demo subapp to see if it works</button>
 | 
					 | 
				
			||||||
				</Buttons>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		</Application>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {useCurtain} from "../src/Components/Curtains/CurtainInstance";
 | 
					 | 
				
			||||||
import {X} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Demo curtain component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function DemoCurtain()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const {close} = useCurtain();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<button className={"close"} onClick={close}><X/> Close the curtain</button>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,26 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {useErrorBoundary} from "react-error-boundary";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A simple demo failing component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function DemoFailingComponent()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	throw new Error("Proudly thrown error.");
 | 
					 | 
				
			||||||
	return (<p>I will never be shown...</p>);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A simple demo reset component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function DemoResetComponent()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get error boundary.
 | 
					 | 
				
			||||||
	const errorBoundary = useErrorBoundary();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<button type={"button"} onClick={() => {
 | 
					 | 
				
			||||||
			errorBoundary.resetBoundary();
 | 
					 | 
				
			||||||
		}}>Reset to try again!</button>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
import {createKernelContext} from "../src/KernelGlobalContext";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create kernel context.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const KernelContext = createKernelContext("");
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,19 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Modal, useModal} from "../src/Components/Modals/Modals";
 | 
					 | 
				
			||||||
import {ModalType} from "../src/Components/Modals/ModalsTypes";
 | 
					 | 
				
			||||||
import {Buttons} from "../src/Components/Buttons/Buttons";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function DemoModal({type}: { type: ModalType; })
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const {close} = useModal();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Modal type={type} title={"Modal title"}>
 | 
					 | 
				
			||||||
			Modal test content
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Buttons>
 | 
					 | 
				
			||||||
				<button onClick={close}>OK</button>
 | 
					 | 
				
			||||||
			</Buttons>
 | 
					 | 
				
			||||||
		</Modal>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,31 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Subapp, useSubapp} from "../src/Components/Subapps/Subapps";
 | 
					 | 
				
			||||||
import {Card} from "../src/Components/Card";
 | 
					 | 
				
			||||||
import {useKernelContext} from "../src/KernelGlobalContext";
 | 
					 | 
				
			||||||
import {KernelContext} from "./DemoKernelContext";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A demo Subapp component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function DemoSubapp()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get subapp close function.
 | 
					 | 
				
			||||||
	const {uuid, close} = useSubapp();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get kernel context data.
 | 
					 | 
				
			||||||
	const [kernelContext] = useKernelContext(KernelContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Subapp title={"My complex subapp"}>
 | 
					 | 
				
			||||||
			<Card>
 | 
					 | 
				
			||||||
				<p>This is a complex subapp.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<p>UUID : <code>{uuid}</code></p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{kernelContext && <p><strong>Kernel context data</strong>: <code>{kernelContext}</code></p>}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<button onClick={close}>Close the subapp</button>
 | 
					 | 
				
			||||||
			</Card>
 | 
					 | 
				
			||||||
		</Subapp>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Card} from "../src/Components/Card";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Navigation test component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function NavTest()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Card>
 | 
					 | 
				
			||||||
			This is a navigation test.
 | 
					 | 
				
			||||||
		</Card>
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,37 +0,0 @@
 | 
				
			||||||
import "../index";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {createRoot} from "react-dom/client";
 | 
					 | 
				
			||||||
import {DemoApp} from "./DemoApp";
 | 
					 | 
				
			||||||
import {createBrowserRouter} from "react-router-dom";
 | 
					 | 
				
			||||||
import {Kernel} from "../src/Application/Kernel";
 | 
					 | 
				
			||||||
import {NavTest} from "./NavTest";
 | 
					 | 
				
			||||||
import {Avocado} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {ApplicationError} from "../src/Application/ApplicationError";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Router initialization.
 | 
					 | 
				
			||||||
const router = createBrowserRouter([
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		path: "/",
 | 
					 | 
				
			||||||
		element: <DemoApp />,
 | 
					 | 
				
			||||||
		children: [
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				path: "test",
 | 
					 | 
				
			||||||
				element: <NavTest />,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		],
 | 
					 | 
				
			||||||
		errorElement: <ApplicationError />,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
document.addEventListener("DOMContentLoaded", () => {
 | 
					 | 
				
			||||||
	const demoApp = document.getElementById("demo-app");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const root = createRoot(demoApp);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	root.render(<Kernel router={router} footer={
 | 
					 | 
				
			||||||
		<footer>
 | 
					 | 
				
			||||||
			<Avocado weight={"duotone"} size={32} />
 | 
					 | 
				
			||||||
			<div>Kernel</div>
 | 
					 | 
				
			||||||
		</footer>
 | 
					 | 
				
			||||||
	} />);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										26
									
								
								eslint.config.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								eslint.config.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
 | 
				
			||||||
 | 
					import storybook from "eslint-plugin-storybook";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import js from '@eslint/js'
 | 
				
			||||||
 | 
					import globals from 'globals'
 | 
				
			||||||
 | 
					import reactHooks from 'eslint-plugin-react-hooks'
 | 
				
			||||||
 | 
					import reactRefresh from 'eslint-plugin-react-refresh'
 | 
				
			||||||
 | 
					import tseslint from 'typescript-eslint'
 | 
				
			||||||
 | 
					import { globalIgnores } from 'eslint/config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default tseslint.config([
 | 
				
			||||||
 | 
					  globalIgnores(['dist']),
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    files: ['**/*.{ts,tsx}'],
 | 
				
			||||||
 | 
					    extends: [
 | 
				
			||||||
 | 
					      js.configs.recommended,
 | 
				
			||||||
 | 
					      tseslint.configs.recommended,
 | 
				
			||||||
 | 
					      reactHooks.configs['recommended-latest'],
 | 
				
			||||||
 | 
					      reactRefresh.configs.vite,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    languageOptions: {
 | 
				
			||||||
 | 
					      ecmaVersion: 2020,
 | 
				
			||||||
 | 
					      globals: globals.browser,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					], storybook.configs["flat/recommended"]);
 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
	<title>UIKernel - Demo</title>
 | 
					 | 
				
			||||||
	<script type="module" src="demo/demo.tsx"></script>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body id="demo-app">
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
							
								
								
									
										53
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										53
									
								
								index.ts
									
										
									
									
									
								
							| 
						 | 
					@ -1,53 +0,0 @@
 | 
				
			||||||
import "./src/styles/main.less";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export * from "./src/Application/Application";
 | 
					 | 
				
			||||||
export * from "./src/Application/ApplicationError";
 | 
					 | 
				
			||||||
export * from "./src/Application/Kernel";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export * from "./src/Components/Box";
 | 
					 | 
				
			||||||
export * from "./src/Components/Card";
 | 
					 | 
				
			||||||
export * from "./src/Components/Buttons/Buttons";
 | 
					 | 
				
			||||||
export * from "./src/Components/Curtains/Curtains";
 | 
					 | 
				
			||||||
export {useCurtain} from "./src/Components/Curtains/CurtainInstance";
 | 
					 | 
				
			||||||
export type {CurtainContextState} from "./src/Components/Curtains/CurtainInstance";
 | 
					 | 
				
			||||||
export * from "./src/Components/Dates/Calendar";
 | 
					 | 
				
			||||||
export * from "./src/Components/Dates/Datepicker";
 | 
					 | 
				
			||||||
export * from "./src/Components/Errors/NotifyErrorsBoundary";
 | 
					 | 
				
			||||||
export * from "./src/Components/Floating/Float";
 | 
					 | 
				
			||||||
export * from "./src/Components/Floating/Tooltip";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/Checkbox";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/CustomValidationRule";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/DatepickerInput";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/PasswordInput";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/Radio";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/RequiredField";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/TimepickerInput";
 | 
					 | 
				
			||||||
export * from "./src/Components/Forms/ToggleSwitch";
 | 
					 | 
				
			||||||
export * from "./src/Components/Loaders/GenericLoader";
 | 
					 | 
				
			||||||
export * from "./src/Components/Loaders/ListLoader";
 | 
					 | 
				
			||||||
export * from "./src/Components/Loaders/SpinningLoader";
 | 
					 | 
				
			||||||
export * from "./src/Components/Menus/AppsMenu";
 | 
					 | 
				
			||||||
export * from "./src/Components/Menus/MainMenu";
 | 
					 | 
				
			||||||
export * from "./src/Components/Menus/MainMenuItem";
 | 
					 | 
				
			||||||
export * from "./src/Components/Menus/Submenu";
 | 
					 | 
				
			||||||
export * from "./src/Components/Menus/SubmenuFloat";
 | 
					 | 
				
			||||||
export * from "./src/Components/Menus/SubmenuItem";
 | 
					 | 
				
			||||||
export * from "./src/Components/Modals/Modals";
 | 
					 | 
				
			||||||
export * from "./src/Components/Modals/ModalsTypes";
 | 
					 | 
				
			||||||
export * from "./src/Components/Notifications/Notification";
 | 
					 | 
				
			||||||
export * from "./src/Components/Notifications/Notifications";
 | 
					 | 
				
			||||||
export * from "./src/Components/Pagination/Paginate";
 | 
					 | 
				
			||||||
export * from "./src/Components/Pagination/Pagination";
 | 
					 | 
				
			||||||
export * from "./src/Components/Select/OptionsSuggestions";
 | 
					 | 
				
			||||||
export * from "./src/Components/Select/Select";
 | 
					 | 
				
			||||||
export * from "./src/Components/Select/SimpleSuggestions";
 | 
					 | 
				
			||||||
export * from "./src/Components/Select/Suggestible";
 | 
					 | 
				
			||||||
export * from "./src/Components/Steps/Steps";
 | 
					 | 
				
			||||||
export * from "./src/Components/Steps/StepsContext";
 | 
					 | 
				
			||||||
export * from "./src/Components/Subapps/Subapps";
 | 
					 | 
				
			||||||
export * from "./src/Components/Tips/Tip";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export * from "./src/Async";
 | 
					 | 
				
			||||||
export * from "./src/GlobalState";
 | 
					 | 
				
			||||||
export * from "./src/KernelGlobalContext";
 | 
					 | 
				
			||||||
export * from "./src/Utils";
 | 
					 | 
				
			||||||
							
								
								
									
										57
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										57
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -4,47 +4,46 @@
 | 
				
			||||||
	"description": "Kernel UI Core.",
 | 
						"description": "Kernel UI Core.",
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"dev": "vite",
 | 
							"dev": "vite",
 | 
				
			||||||
		"build": "tsc && vite build"
 | 
							"build": "tsc && vite build",
 | 
				
			||||||
 | 
							"storybook": "storybook dev -p 6006",
 | 
				
			||||||
 | 
							"storybook:build": "storybook build"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"type": "module",
 | 
						"type": "module",
 | 
				
			||||||
	"source": "index.ts",
 | 
						"source": "index.ts",
 | 
				
			||||||
	"types": "lib/index.d.ts",
 | 
						"types": "lib/index.d.ts",
 | 
				
			||||||
	"main": "lib/index.js",
 | 
						"main": "lib/index.js",
 | 
				
			||||||
	"files": [
 | 
						"files": [
 | 
				
			||||||
		"lib/**/*"
 | 
							"lib/**/*",
 | 
				
			||||||
 | 
							"README.md",
 | 
				
			||||||
 | 
							"LICENSE"
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
	"publishConfig": {
 | 
						"publishConfig": {
 | 
				
			||||||
		"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
 | 
							"@kernelui:registry": "https://code.zeptotech.net/api/packages/UIKernel/npm/"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"dependencies": {
 | 
					 | 
				
			||||||
		"@floating-ui/react": "^0.26.17",
 | 
					 | 
				
			||||||
		"@fontsource-variable/jetbrains-mono": "^5.0.21",
 | 
					 | 
				
			||||||
		"@fontsource-variable/manrope": "^5.0.20",
 | 
					 | 
				
			||||||
		"@fontsource-variable/source-serif-4": "^5.0.19",
 | 
					 | 
				
			||||||
		"react-error-boundary": "^4.0.13",
 | 
					 | 
				
			||||||
		"react-merge-refs": "^2.1.1",
 | 
					 | 
				
			||||||
		"uuid": "^10.0.0"
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	"devDependencies": {
 | 
						"devDependencies": {
 | 
				
			||||||
		"@phosphor-icons/react": "^2.1.7",
 | 
							"@phosphor-icons/react": "^2.1.10",
 | 
				
			||||||
		"@types/node": "^22.7.4",
 | 
							"@storybook/addon-docs": "^9.1.5",
 | 
				
			||||||
		"@types/react": "^18.3.12",
 | 
							"@storybook/react-vite": "^9.1.5",
 | 
				
			||||||
		"@types/react-dom": "^18.3.1",
 | 
							"@types/node": "^24.3.1",
 | 
				
			||||||
		"@types/uuid": "^10",
 | 
							"@types/react": "^19.1.12",
 | 
				
			||||||
		"@vitejs/plugin-react": "^4.3.4",
 | 
							"@types/react-dom": "^19.1.9",
 | 
				
			||||||
		"less": "^4.2.0",
 | 
							"@vitejs/plugin-react": "^5.0.2",
 | 
				
			||||||
		"react": "^18.3.1",
 | 
							"eslint": "^9.35.0",
 | 
				
			||||||
		"react-dom": "^18.3.1",
 | 
							"eslint-plugin-storybook": "^9.1.5",
 | 
				
			||||||
		"react-router-dom": "^7.0.1",
 | 
							"prettier": "^3.6.2",
 | 
				
			||||||
		"typescript": "^5.6.2",
 | 
							"react": "^19.1.1",
 | 
				
			||||||
		"vite": "^6.0.1",
 | 
							"react-dom": "^19.1.1",
 | 
				
			||||||
		"vite-plugin-dts": "^4.3.0"
 | 
							"react-router-dom": "^7.8.2",
 | 
				
			||||||
 | 
							"storybook": "^9.1.5",
 | 
				
			||||||
 | 
							"typescript": "^5.9.2",
 | 
				
			||||||
 | 
							"vite": "^7.1.5",
 | 
				
			||||||
 | 
							"vite-plugin-dts": "^4.5.4"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"peerDependencies": {
 | 
						"peerDependencies": {
 | 
				
			||||||
		"@phosphor-icons/react": "^2.1.7",
 | 
							"@phosphor-icons/react": "^2.1.10",
 | 
				
			||||||
		"react": "^18.3.1",
 | 
							"react": "^19.1.1",
 | 
				
			||||||
		"react-dom": "^18.3.1",
 | 
							"react-dom": "^19.1.1",
 | 
				
			||||||
		"react-router-dom": "^7.0.1"
 | 
							"react-router-dom": "^7.8.2"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"packageManager": "yarn@4.5.3"
 | 
						"packageManager": "yarn@4.9.4"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {ApplicationError, ApplicationErrorBoundary} from "./ApplicationError";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main Kernel UI application.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Application({errorElement, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	errorElement?: React.ReactNode;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<ApplicationErrorBoundary errorElement={errorElement ?? <ApplicationError />}>
 | 
					 | 
				
			||||||
			<main className={"app"}>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</main>
 | 
					 | 
				
			||||||
		</ApplicationErrorBoundary>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,94 +0,0 @@
 | 
				
			||||||
import React, {useContext, useState} from "react";
 | 
					 | 
				
			||||||
import {ArrowsClockwise, Bug, BugDroid} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {useRouteError} from "react-router-dom";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Application error context.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const ApplicationErrorContext = React.createContext<Error>(undefined);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get current error from context or router.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useApplicationError(): Error
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get error from context or router.
 | 
					 | 
				
			||||||
	const error = useContext(ApplicationErrorContext);
 | 
					 | 
				
			||||||
	const routeError = useRouteError() as Error;
 | 
					 | 
				
			||||||
	return error ?? routeError;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Application error component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function ApplicationError({children}: {
 | 
					 | 
				
			||||||
	children?: (error: Error) => React.ReactElement;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get error from context.
 | 
					 | 
				
			||||||
	const error = useApplicationError();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Show details state.
 | 
					 | 
				
			||||||
	const [showDetails, setShowDetails] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<main className={"error"}>
 | 
					 | 
				
			||||||
			{children ? children(error) : (
 | 
					 | 
				
			||||||
				<>
 | 
					 | 
				
			||||||
					<Bug size={64} weight={"duotone"} />
 | 
					 | 
				
			||||||
					<h1>Error</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<hr />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<h2>{error.name}</h2>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<p>An unexpected error happened and the application was forced to quit.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<pre className={"error"}>{error.message}</pre>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<button onClick={() => window.location.reload()}><ArrowsClockwise size={20} /> Restart application</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<div className={"details"}>
 | 
					 | 
				
			||||||
						<button onClick={() => setShowDetails(!showDetails)}><BugDroid size={18} /> {showDetails ? "Hide" : "Show"} details</button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						{ // Show details if required.
 | 
					 | 
				
			||||||
							showDetails && (
 | 
					 | 
				
			||||||
								<pre>
 | 
					 | 
				
			||||||
									{error.stack}
 | 
					 | 
				
			||||||
								</pre>
 | 
					 | 
				
			||||||
							)
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					</div>
 | 
					 | 
				
			||||||
				</>
 | 
					 | 
				
			||||||
			)}
 | 
					 | 
				
			||||||
		</main>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Error boundary component for the application.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class ApplicationErrorBoundary extends React.Component<React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	errorElement: React.ReactNode;
 | 
					 | 
				
			||||||
}>, {
 | 
					 | 
				
			||||||
	error: Error;
 | 
					 | 
				
			||||||
}>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	static getDerivedStateFromError(error: Error)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return { error: error };
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	render()
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		if (this.state?.error)
 | 
					 | 
				
			||||||
			// An error happened, showing the application error content.
 | 
					 | 
				
			||||||
			return (
 | 
					 | 
				
			||||||
				<ApplicationErrorContext.Provider value={this.state?.error}>
 | 
					 | 
				
			||||||
					{this.props.errorElement}
 | 
					 | 
				
			||||||
				</ApplicationErrorContext.Provider>
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return this.props.children;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
import { IconContext } from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {createBrowserRouter, RouterProvider} from "react-router-dom";
 | 
					 | 
				
			||||||
import {CurtainsProvider} from "../Components/Curtains/Curtains";
 | 
					 | 
				
			||||||
import {NotificationsProvider} from "../Components/Notifications/Notifications";
 | 
					 | 
				
			||||||
import {KernelGlobalContextProvider} from "../KernelGlobalContext";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main Kernel UI app component which initializes everything.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Kernel({header, footer, router}: {
 | 
					 | 
				
			||||||
	header?: React.ReactNode;
 | 
					 | 
				
			||||||
	footer?: React.ReactNode;
 | 
					 | 
				
			||||||
	router: ReturnType<typeof createBrowserRouter>;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<KernelGlobalContextProvider>
 | 
					 | 
				
			||||||
			<IconContext.Provider value={{
 | 
					 | 
				
			||||||
				size: "1em",
 | 
					 | 
				
			||||||
				weight: "bold",
 | 
					 | 
				
			||||||
			}}>
 | 
					 | 
				
			||||||
				<NotificationsProvider>
 | 
					 | 
				
			||||||
					<CurtainsProvider>
 | 
					 | 
				
			||||||
						{header}
 | 
					 | 
				
			||||||
						<RouterProvider router={router} />
 | 
					 | 
				
			||||||
						{footer}
 | 
					 | 
				
			||||||
					</CurtainsProvider>
 | 
					 | 
				
			||||||
				</NotificationsProvider>
 | 
					 | 
				
			||||||
			</IconContext.Provider>
 | 
					 | 
				
			||||||
		</KernelGlobalContextProvider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										158
									
								
								src/Async.tsx
									
										
									
									
									
								
							
							
						
						
									
										158
									
								
								src/Async.tsx
									
										
									
									
									
								
							| 
						 | 
					@ -1,158 +0,0 @@
 | 
				
			||||||
import React, {useEffect, useMemo, useState} from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A type that can be returned by a promise or as is.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type Promisable<T> = T|Promise<T>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Asynchronous data state.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
interface AsyncState<T>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Determine if we are waiting for the promise result or not.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	pending: boolean;
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The promise which is retrieved (or has retrieved) data.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	promise: Promisable<T>;
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Error thrown by the promise.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	error: Error;
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The promise result.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	data: T;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A promise production function.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type PromiseFn<T> = () => Promise<T>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * React hook for promise result retrieval.
 | 
					 | 
				
			||||||
 * @param promise The promise or a function which produces a promise.
 | 
					 | 
				
			||||||
 * @param deps When one of the `deps` change, it will wait for the promise again.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useAsync<T>(promise: Promisable<T>|PromiseFn<T>, deps: any[] = []): [AsyncState<T>, React.Dispatch<T>]
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the actual promise from the function if there is one.
 | 
					 | 
				
			||||||
	promise = useMemo(() => {
 | 
					 | 
				
			||||||
		if ((promise as PromiseFn<T>)?.call)
 | 
					 | 
				
			||||||
			return (promise as PromiseFn<T>)();
 | 
					 | 
				
			||||||
		else if (promise instanceof Promise)
 | 
					 | 
				
			||||||
			return Promise.race([promise as Promise<T>]);
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
			return promise;
 | 
					 | 
				
			||||||
	}, deps);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The async state.
 | 
					 | 
				
			||||||
	const [state, setState] = useState<AsyncState<T>>({
 | 
					 | 
				
			||||||
		pending: promise instanceof Promise,
 | 
					 | 
				
			||||||
		promise: promise as Promisable<T>,
 | 
					 | 
				
			||||||
		error: undefined,
 | 
					 | 
				
			||||||
		data: promise instanceof Promise ? undefined : promise as T,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Partial update of an async state.
 | 
					 | 
				
			||||||
	 * @param stateUpdate A partial update object.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const updateState = (stateUpdate: Partial<AsyncState<T>>) => {
 | 
					 | 
				
			||||||
		// Copy the original state and apply the partial state update.
 | 
					 | 
				
			||||||
		setState(Object.assign({}, state, stateUpdate));
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Reconfigure the promise when any deps have changed.
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		if (!(promise instanceof Promise))
 | 
					 | 
				
			||||||
		{ // If it's not a promise, there is nothing to wait for.
 | 
					 | 
				
			||||||
			updateState({
 | 
					 | 
				
			||||||
				pending: false,
 | 
					 | 
				
			||||||
				promise: promise as Promisable<T>,
 | 
					 | 
				
			||||||
				error: undefined,
 | 
					 | 
				
			||||||
				data: promise as T,
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		promise.then((result) => {
 | 
					 | 
				
			||||||
			// When there is a result, disable pending state and set retrieved data, without error.
 | 
					 | 
				
			||||||
			updateState({
 | 
					 | 
				
			||||||
				pending: false,
 | 
					 | 
				
			||||||
				error: undefined,
 | 
					 | 
				
			||||||
				data: result,
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}).catch((error) => {
 | 
					 | 
				
			||||||
			// An error happened, disable pending state, reset data, and set the error.
 | 
					 | 
				
			||||||
			updateState({
 | 
					 | 
				
			||||||
				pending: false,
 | 
					 | 
				
			||||||
				error: error,
 | 
					 | 
				
			||||||
				data: undefined,
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Promise is ready: reset the state to pending with the configured promise, without data and error.
 | 
					 | 
				
			||||||
		updateState({
 | 
					 | 
				
			||||||
			pending: true,
 | 
					 | 
				
			||||||
			promise: promise,
 | 
					 | 
				
			||||||
			error: undefined,
 | 
					 | 
				
			||||||
			data: undefined,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}, deps);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Return the current async state and a dispatch data function.
 | 
					 | 
				
			||||||
	return [state, (data: T) => {
 | 
					 | 
				
			||||||
		updateState({data: data});
 | 
					 | 
				
			||||||
	}];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Wait for the promise to be fulfilled to render the children.
 | 
					 | 
				
			||||||
 * @param async The async state.
 | 
					 | 
				
			||||||
 * @param children Renderer function of the children, takes promised data as argument.
 | 
					 | 
				
			||||||
 * @param fallback Content shown when the promise is not fulfilled yet.
 | 
					 | 
				
			||||||
 * @constructor
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Await<T>({ async, children, fallback }: {
 | 
					 | 
				
			||||||
	async: AsyncState<T>;
 | 
					 | 
				
			||||||
	children: (async: T) => React.ReactElement;
 | 
					 | 
				
			||||||
	fallback?: React.ReactElement;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Still waiting for the promised data, showing fallback content.
 | 
					 | 
				
			||||||
	if (async.pending) return fallback ?? <></>;
 | 
					 | 
				
			||||||
	// An error happened, throwing it.
 | 
					 | 
				
			||||||
	if (async.error) throw async.error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Promise is fulfilled, rendering the children with result data.
 | 
					 | 
				
			||||||
	return children(async.data);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Easy async component with fallback and fulfilled children render.
 | 
					 | 
				
			||||||
 * @param promise The promise.
 | 
					 | 
				
			||||||
 * @param children Renderer function of the children, takes promised data as argument.
 | 
					 | 
				
			||||||
 * @param fallback Content shown when the promise is not fulfilled yet.
 | 
					 | 
				
			||||||
 * @constructor
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Async<T>({promise, fallback, children}: {
 | 
					 | 
				
			||||||
	promise: Promisable<T>|PromiseFn<T>;
 | 
					 | 
				
			||||||
	children: (async: T) => React.ReactElement;
 | 
					 | 
				
			||||||
	fallback?: React.ReactElement;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get async data from given promise.
 | 
					 | 
				
			||||||
	const [async] = useAsync(promise, [promise]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		// Wait for promised data.
 | 
					 | 
				
			||||||
		<Await async={async} fallback={fallback ?? undefined}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</Await>
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React, {PropsWithChildren} from "react";
 | 
					 | 
				
			||||||
import {classes} from "../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Content box generic component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Box({children, className, ...props}: PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("box", className)} {...props}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Buttons({placement, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	placement?: "right"|"left"|"center";
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Default placement: right.
 | 
					 | 
				
			||||||
	placement = placement ?? "right";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("buttons", placement)}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React, {PropsWithChildren} from "react";
 | 
					 | 
				
			||||||
import {classes} from "../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Content card component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Card({children, className, ...props}: PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("card", className)} {...props}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,79 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useContext, useMemo, useRef} from "react";
 | 
					 | 
				
			||||||
import {CurtainUuidType, useCurtains} from "./Curtains";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Current curtain data and functions.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface CurtainContextState
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Curtain UUID.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	uuid: CurtainUuidType;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Close the curtain.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	close: () => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * True if the curtain is closed (while transitioning out).
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	closed: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Current curtain context.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const CurtainContext = React.createContext<CurtainContextState>({
 | 
					 | 
				
			||||||
	// Empty values.
 | 
					 | 
				
			||||||
	uuid: "",
 | 
					 | 
				
			||||||
	close: () => {},
 | 
					 | 
				
			||||||
	closed: false,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook to access current curtain data and functions.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useCurtain(): CurtainContextState
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return useContext(CurtainContext);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Component of an opened curtain instance.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function CurtainInstance({uuid, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Curtain UUID.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	uuid: string;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get close curtain function.
 | 
					 | 
				
			||||||
	const {close, isClosed} = useCurtains();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize close curtain function.
 | 
					 | 
				
			||||||
	const closeCurtain = useRef<() => void>();
 | 
					 | 
				
			||||||
	closeCurtain.current = useCallback(() => {
 | 
					 | 
				
			||||||
		// Close the current curtain.
 | 
					 | 
				
			||||||
		close(uuid);
 | 
					 | 
				
			||||||
	}, [uuid, close]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize context state from action functions.
 | 
					 | 
				
			||||||
	const contextState = useMemo(() => ({
 | 
					 | 
				
			||||||
		uuid: uuid,
 | 
					 | 
				
			||||||
		close: () => closeCurtain.current(),
 | 
					 | 
				
			||||||
		closed: isClosed(uuid),
 | 
					 | 
				
			||||||
	}), [uuid, closeCurtain, isClosed]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<CurtainContext.Provider value={contextState}>
 | 
					 | 
				
			||||||
			<div className={classes("curtain", isClosed(uuid) ? "closed" : undefined)}>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
		</CurtainContext.Provider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,207 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
 | 
					 | 
				
			||||||
import ReactDOM from "react-dom";
 | 
					 | 
				
			||||||
import {v4 as uuidv4} from "uuid";
 | 
					 | 
				
			||||||
import {CurtainInstance} from "./CurtainInstance";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Curtain UUID type.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type CurtainUuidType = string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The function that opens a curtain.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type OpenCurtainFunction = (content: React.ReactNode) => CurtainUuidType;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The function that closes a curtain.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type CloseCurtainFunction = (uuid: CurtainUuidType) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The function that checks if a curtain is closed (while transitioning out) or not.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type IsCurtainClosedFunction = (uuid: CurtainUuidType) => boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Interface of curtains state.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface CurtainsContextState
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Open a new curtain.
 | 
					 | 
				
			||||||
	 * @param content The curtain content.
 | 
					 | 
				
			||||||
	 * @return UUID of the curtain.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	open: OpenCurtainFunction;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Close the given curtain.
 | 
					 | 
				
			||||||
	 * @param uuid UUID of the curtain to close.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	close: CloseCurtainFunction;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Check if the given curtain is closed or not.
 | 
					 | 
				
			||||||
	 * @param uuid UUID of the curtain to check.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	isClosed: IsCurtainClosedFunction;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CurtainsContext = React.createContext<CurtainsContextState>({
 | 
					 | 
				
			||||||
	// Empty functions.
 | 
					 | 
				
			||||||
	open() { return ""; },
 | 
					 | 
				
			||||||
	close() {},
 | 
					 | 
				
			||||||
	isClosed() { return false; },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook to interact with curtains (open or close).
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useCurtains(): CurtainsContextState
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return useContext(CurtainsContext);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A generic callable curtain element.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type CallableCurtainElement = (...args: any) => React.ReactNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A callable curtain function with typed parameters.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type CallableCurtain<F extends CallableCurtainElement> = (...args: Parameters<F>) => CurtainUuidType;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Callable curtain function generator.
 | 
					 | 
				
			||||||
 * @param curtains The curtains context state.
 | 
					 | 
				
			||||||
 * @param curtainElement The curtain element to open when called.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function callableCurtain<F extends CallableCurtainElement>(curtains: CurtainsContextState, curtainElement: React.ReactNode|F): CallableCurtain<F>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	if (typeof curtainElement == "function")
 | 
					 | 
				
			||||||
		// It's a callable curtain element, the callable curtain should be called with the same parameters.
 | 
					 | 
				
			||||||
		return (...args: Parameters<F>) => curtains.open(curtainElement(...args));
 | 
					 | 
				
			||||||
	else
 | 
					 | 
				
			||||||
		// It's a simple element, just open it.
 | 
					 | 
				
			||||||
		return () => curtains.open(curtainElement);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook to create a simple curtain.
 | 
					 | 
				
			||||||
 * @param curtainElement Content of the curtain to open.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useCallableCurtain<F extends CallableCurtainElement>(curtainElement: React.ReactNode|F): CallableCurtain<F>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get curtains context state.
 | 
					 | 
				
			||||||
	const curtains = useCurtains();
 | 
					 | 
				
			||||||
	// Create and keep the curtain callable in memory.
 | 
					 | 
				
			||||||
	return useCallback(callableCurtain(curtains, curtainElement), [curtains, curtainElement]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Page curtains provider.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function CurtainsProvider({children}: React.PropsWithChildren<{}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Curtains state.
 | 
					 | 
				
			||||||
	const [curtains, setCurtains] = useState<Record<CurtainUuidType, React.ReactNode>>({});
 | 
					 | 
				
			||||||
	// Keeping track of closed curtains that are still on (while transitioning out).
 | 
					 | 
				
			||||||
	const [closedCurtains, setClosedCurtains] = useState<Record<CurtainUuidType, boolean>>({});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize open curtain function.
 | 
					 | 
				
			||||||
	const open = useRef<OpenCurtainFunction>();
 | 
					 | 
				
			||||||
	open.current = useCallback((content: React.ReactNode) => {
 | 
					 | 
				
			||||||
		// Generate a new curtain UUID for the new curtain to open.
 | 
					 | 
				
			||||||
		const curtainUuid = uuidv4();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Add the curtain to open to the list of curtains, with the generated UUID.
 | 
					 | 
				
			||||||
		setCurtains({
 | 
					 | 
				
			||||||
			...curtains,
 | 
					 | 
				
			||||||
			[curtainUuid]: content,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Return the curtain UUID.
 | 
					 | 
				
			||||||
		return curtainUuid;
 | 
					 | 
				
			||||||
	}, [curtains, setCurtains]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize remove curtain function.
 | 
					 | 
				
			||||||
	const remove = useRef<CloseCurtainFunction>();
 | 
					 | 
				
			||||||
	remove.current = useCallback((uuid: CurtainUuidType) => {
 | 
					 | 
				
			||||||
		// Copy the curtains list.
 | 
					 | 
				
			||||||
		const newCurtains = {...curtains};
 | 
					 | 
				
			||||||
		const newClosedCurtains = {...closedCurtains};
 | 
					 | 
				
			||||||
		// Remove the given curtain from the list.
 | 
					 | 
				
			||||||
		delete newCurtains[uuid];
 | 
					 | 
				
			||||||
		delete newClosedCurtains[uuid];
 | 
					 | 
				
			||||||
		// Set the new curtains list.
 | 
					 | 
				
			||||||
		setCurtains(newCurtains);
 | 
					 | 
				
			||||||
		setClosedCurtains(newClosedCurtains);
 | 
					 | 
				
			||||||
	}, [curtains, setCurtains, closedCurtains, setClosedCurtains]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize close curtain function with animation.
 | 
					 | 
				
			||||||
	const close = useRef<CloseCurtainFunction>();
 | 
					 | 
				
			||||||
	close.current = useCallback((uuid) => {
 | 
					 | 
				
			||||||
		// Add the given curtain UUID to the list of closed curtains.
 | 
					 | 
				
			||||||
		setClosedCurtains({
 | 
					 | 
				
			||||||
			...closedCurtains,
 | 
					 | 
				
			||||||
			[uuid]: true,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Remove the curtain 1s later.
 | 
					 | 
				
			||||||
		window.setTimeout(() => {
 | 
					 | 
				
			||||||
			// Remove the curtain.
 | 
					 | 
				
			||||||
			remove.current(uuid);
 | 
					 | 
				
			||||||
		}, 1000);
 | 
					 | 
				
			||||||
	}, [remove, closedCurtains, setClosedCurtains]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize isClosed curtain function.
 | 
					 | 
				
			||||||
	const isClosed = useRef<IsCurtainClosedFunction>();
 | 
					 | 
				
			||||||
	isClosed.current = useCallback((uuid) => (!!closedCurtains?.[uuid]), [closedCurtains]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize context state from action functions.
 | 
					 | 
				
			||||||
	const contextState = useMemo(() => ({
 | 
					 | 
				
			||||||
		open: (content: React.ReactNode) => open.current(content),
 | 
					 | 
				
			||||||
		close: (uuid: CurtainUuidType) => close.current(uuid),
 | 
					 | 
				
			||||||
		isClosed: (uuid: CurtainUuidType) => isClosed.current(uuid),
 | 
					 | 
				
			||||||
	}), [open, close, isClosed]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Show dimmed main content.
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		if (
 | 
					 | 
				
			||||||
			Object.keys(curtains).filter((curtainId) => !closedCurtains[curtainId]).length > 0
 | 
					 | 
				
			||||||
		) // We should dim content if there is at least one open curtain.
 | 
					 | 
				
			||||||
		{ // Only dim if it's not already dimmed.
 | 
					 | 
				
			||||||
			if (!document.body.classList.contains("dimmed")) document.body.classList.add("dimmed");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
			// We shouldn't dim content.
 | 
					 | 
				
			||||||
			document.body.classList.remove("dimmed");
 | 
					 | 
				
			||||||
	}, [curtains, closedCurtains]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<CurtainsContext.Provider value={contextState}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
			<CurtainsPortal curtains={curtains} />
 | 
					 | 
				
			||||||
		</CurtainsContext.Provider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Curtains portal manager.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function CurtainsPortal({curtains}: {
 | 
					 | 
				
			||||||
	curtains: Record<CurtainUuidType, React.ReactNode>;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return ReactDOM.createPortal(Object.entries(curtains).map(([uuid, curtainContent]) => (
 | 
					 | 
				
			||||||
		<CurtainInstance key={uuid} uuid={uuid}>
 | 
					 | 
				
			||||||
			{curtainContent}
 | 
					 | 
				
			||||||
		</CurtainInstance>
 | 
					 | 
				
			||||||
	)), document.body);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,108 +0,0 @@
 | 
				
			||||||
import React, {useMemo} from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Calendar component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Calendar({date, onDateSelected, locale, className, ...tableProps}: {
 | 
					 | 
				
			||||||
	date: Date;
 | 
					 | 
				
			||||||
	onDateSelected: (date: Date) => void;
 | 
					 | 
				
			||||||
	locale?: string;
 | 
					 | 
				
			||||||
} & React.TableHTMLAttributes<HTMLTableElement>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	locale = useMemo(() => (locale ?? "fr"), [locale]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const currentMonthHeader = useMemo(() => (
 | 
					 | 
				
			||||||
		<tr>
 | 
					 | 
				
			||||||
			{ // For each day of the week, showing its name.
 | 
					 | 
				
			||||||
				[1, 2, 3, 4, 5, 6, 7].map(day => {
 | 
					 | 
				
			||||||
					// Getting a date with the right day of the week.
 | 
					 | 
				
			||||||
					const dayOfWeek = new Date(1970, 0, 4 + day);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					return (
 | 
					 | 
				
			||||||
						<th key={day}>
 | 
					 | 
				
			||||||
							{(new Intl.DateTimeFormat(locale, {weekday: "short"})).format(dayOfWeek)}
 | 
					 | 
				
			||||||
						</th>
 | 
					 | 
				
			||||||
					);
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</tr>
 | 
					 | 
				
			||||||
	), [date]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const currentMonthTable = useMemo(() => {
 | 
					 | 
				
			||||||
		// Initialize weeks.
 | 
					 | 
				
			||||||
		const weeksRows = [];
 | 
					 | 
				
			||||||
		let currentWeek = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Get start date of the calendar.
 | 
					 | 
				
			||||||
		let currentDate = new Date(date);
 | 
					 | 
				
			||||||
		currentDate.setDate(1); // First day of the month.
 | 
					 | 
				
			||||||
		currentDate.setDate(currentDate.getDate() - (currentDate.getDay() - 1 + 7) % 7); // Searching the start of the first week of the current month.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Get last day of the calendar.
 | 
					 | 
				
			||||||
		const lastDate = new Date(date);
 | 
					 | 
				
			||||||
		lastDate.setMonth(lastDate.getMonth() + 1, 0); // Get the last day of the month.
 | 
					 | 
				
			||||||
		lastDate.setDate(lastDate.getDate() + (7 - lastDate.getDay())); // Searching the end of the last week of the current month.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		while (currentDate.getTime() <= lastDate.getTime() || weeksRows.length < 6)
 | 
					 | 
				
			||||||
		{ // While the current date is before or is the last day of the current view,
 | 
					 | 
				
			||||||
			// adding the current day to the current week.
 | 
					 | 
				
			||||||
			currentWeek.push(
 | 
					 | 
				
			||||||
				<Day key={`${currentDate.getFullYear()}-${currentDate.getMonth()}-${currentDate.getDate()}`}
 | 
					 | 
				
			||||||
				     date={new Date(currentDate)}
 | 
					 | 
				
			||||||
				     faded={date.getMonth() != currentDate.getMonth()}
 | 
					 | 
				
			||||||
				     selected={date.getTime() == currentDate.getTime()}
 | 
					 | 
				
			||||||
				     onClick={onDateSelected} />
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// We're on sunday, adding the current week and creating a new one.
 | 
					 | 
				
			||||||
			if (currentDate.getDay() == 0)
 | 
					 | 
				
			||||||
			{ // The current week is ended, adding it and creating a new one.
 | 
					 | 
				
			||||||
				weeksRows.push(
 | 
					 | 
				
			||||||
					<tr key={`${currentDate.getFullYear()}-${currentDate.getMonth()}-${currentDate.getDate()}`}>
 | 
					 | 
				
			||||||
						{currentWeek}
 | 
					 | 
				
			||||||
					</tr>
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
				currentWeek = []; // Reset the current week to a new one.
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Get the next day.
 | 
					 | 
				
			||||||
			currentDate.setDate(currentDate.getDate() + 1);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Return generated weeks rows.
 | 
					 | 
				
			||||||
		return weeksRows;
 | 
					 | 
				
			||||||
	}, [date, onDateSelected]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<table className={classes("calendar", className)} {...tableProps}>
 | 
					 | 
				
			||||||
			<thead>
 | 
					 | 
				
			||||||
			{currentMonthHeader}
 | 
					 | 
				
			||||||
			</thead>
 | 
					 | 
				
			||||||
			<tbody>
 | 
					 | 
				
			||||||
			{currentMonthTable}
 | 
					 | 
				
			||||||
			</tbody>
 | 
					 | 
				
			||||||
		</table>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Calendar day component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function Day({date, onClick, faded, selected}: {
 | 
					 | 
				
			||||||
	date: Date;
 | 
					 | 
				
			||||||
	onClick?: (date: Date) => void;
 | 
					 | 
				
			||||||
	faded: boolean;
 | 
					 | 
				
			||||||
	selected: boolean;
 | 
					 | 
				
			||||||
}): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<td key={`${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`}>
 | 
					 | 
				
			||||||
			<a className={`day${faded ? " faded" : ""}${selected ? " selected" : ""}`}
 | 
					 | 
				
			||||||
			   onClick={() => onClick?.(date)}>
 | 
					 | 
				
			||||||
				{date.getDate()}
 | 
					 | 
				
			||||||
			</a>
 | 
					 | 
				
			||||||
		</td>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,65 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useMemo} from "react";
 | 
					 | 
				
			||||||
import {CaretLeft, CaretRight} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {Tooltip} from "../Floating/Tooltip";
 | 
					 | 
				
			||||||
import {Calendar} from "./Calendar";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Datepicker component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Datepicker({date, onDateSelected, locale, className, ...divProps}: {
 | 
					 | 
				
			||||||
	date: Date;
 | 
					 | 
				
			||||||
	onDateSelected: (date: Date) => void;
 | 
					 | 
				
			||||||
	locale?: string;
 | 
					 | 
				
			||||||
} & React.HTMLAttributes<HTMLDivElement>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	locale = useMemo(() => (locale ?? "fr"), [locale]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get previous month name.
 | 
					 | 
				
			||||||
	const previousMonthName = useMemo(() => {
 | 
					 | 
				
			||||||
		// Copy the current date and get back one month earlier.
 | 
					 | 
				
			||||||
		const previousMonthDate = new Date(date);
 | 
					 | 
				
			||||||
		previousMonthDate.setMonth(previousMonthDate.getMonth() - 1);
 | 
					 | 
				
			||||||
		return (new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" })).format(previousMonthDate);
 | 
					 | 
				
			||||||
	}, [date]);
 | 
					 | 
				
			||||||
	// Get next month name.
 | 
					 | 
				
			||||||
	const nextMonthName = useMemo(() => {
 | 
					 | 
				
			||||||
		// Copy the current date and go to one month later.
 | 
					 | 
				
			||||||
		const nextMonthDate = new Date(date);
 | 
					 | 
				
			||||||
		nextMonthDate.setMonth(nextMonthDate.getMonth() + 1);
 | 
					 | 
				
			||||||
		return (new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" })).format(nextMonthDate);
 | 
					 | 
				
			||||||
	}, [date]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("datepicker", className)} {...divProps}>
 | 
					 | 
				
			||||||
			<div className={"year-month"}>
 | 
					 | 
				
			||||||
				{(new Intl.DateTimeFormat(locale, {month: "long", year: "numeric"})).format(date)}
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Calendar date={date} onDateSelected={onDateSelected} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Tooltip content={previousMonthName}>
 | 
					 | 
				
			||||||
				<button type={"button"} className={"previous-month"} onClick={
 | 
					 | 
				
			||||||
					useCallback((event) => {
 | 
					 | 
				
			||||||
						const newDate = new Date(date);
 | 
					 | 
				
			||||||
						newDate.setMonth(newDate.getMonth() - 1);
 | 
					 | 
				
			||||||
						onDateSelected(newDate);
 | 
					 | 
				
			||||||
					}, [date, onDateSelected])
 | 
					 | 
				
			||||||
				}>
 | 
					 | 
				
			||||||
					<CaretLeft />
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
			</Tooltip>
 | 
					 | 
				
			||||||
			<Tooltip content={nextMonthName}>
 | 
					 | 
				
			||||||
				<button type={"button"} className={"next-month"} onClick={
 | 
					 | 
				
			||||||
					useCallback((event) => {
 | 
					 | 
				
			||||||
						const newDate = new Date(date);
 | 
					 | 
				
			||||||
						newDate.setMonth(newDate.getMonth() + 1);
 | 
					 | 
				
			||||||
						onDateSelected(newDate);
 | 
					 | 
				
			||||||
					}, [date, onDateSelected])
 | 
					 | 
				
			||||||
				}>
 | 
					 | 
				
			||||||
					<CaretRight />
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
			</Tooltip>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,36 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useMemo} from "react";
 | 
					 | 
				
			||||||
import {ErrorBoundary, ErrorBoundaryProps} from "react-error-boundary";
 | 
					 | 
				
			||||||
import {useNotify} from "../Notifications/Notifications";
 | 
					 | 
				
			||||||
import {Notification, NotificationType} from "../Notifications/Notification";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A React error boundary that show errors in notifications.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function NotifyErrorsBoundary({children, onError, fallback, fallbackRender, FallbackComponent, ...props}: React.PropsWithChildren<Partial<ErrorBoundaryProps>>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get notification function.
 | 
					 | 
				
			||||||
	const notify = useNotify();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Error handling function.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const handleError = useCallback((error: Error, info: React.ErrorInfo) => {
 | 
					 | 
				
			||||||
		// Show a notification about the error.
 | 
					 | 
				
			||||||
		notify(<Notification type={NotificationType.ERROR}>Unexpected error: {error.message}</Notification>);
 | 
					 | 
				
			||||||
		// Then call defined onError, if there is one.
 | 
					 | 
				
			||||||
		onError?.(error, info);
 | 
					 | 
				
			||||||
	}, [onError]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Define default fallback component.
 | 
					 | 
				
			||||||
	const defaultFallback = useMemo(() => <></>, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!fallback && !fallbackRender && !FallbackComponent)
 | 
					 | 
				
			||||||
		// Set default fallback if nothing is set.
 | 
					 | 
				
			||||||
		fallback = defaultFallback;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<ErrorBoundary onError={handleError} fallback={fallback} {...props}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</ErrorBoundary>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,146 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useMemo, useState} from "react";
 | 
					 | 
				
			||||||
import {Card} from "../Card";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
	flip,
 | 
					 | 
				
			||||||
	shift,
 | 
					 | 
				
			||||||
	useClick, useDismiss,
 | 
					 | 
				
			||||||
	useFloating, UseFloatingOptions,
 | 
					 | 
				
			||||||
	useFocus,
 | 
					 | 
				
			||||||
	useHover,
 | 
					 | 
				
			||||||
	useInteractions, useRole, useTransitionStyles
 | 
					 | 
				
			||||||
} from "@floating-ui/react";
 | 
					 | 
				
			||||||
import {mergeRefs} from "react-merge-refs";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Fully managed floating content function.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type Managed<T = React.ReactElement|React.ReactNode> = (show: () => void, hide: () => void) => T;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Allowed floating modes.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type FloatingMode = "always"|"click"|"hover"|"focus"|"managed";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Role of a floating element.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type FloatRole = "tooltip" | "dialog" | "alertdialog" | "menu" | "listbox" | "grid" | "tree" | "select" | "label" | "combobox";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type of the element on which the floating element is based.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type FloatChild = (React.ReactElement & React.ClassAttributes<HTMLElement>)|Managed<(React.ReactElement & React.ClassAttributes<HTMLElement>)>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Properties of the Float component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface FloatProperties
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	children: FloatChild;
 | 
					 | 
				
			||||||
	content?: React.ReactNode|Managed<React.ReactNode>;
 | 
					 | 
				
			||||||
	className?: string;
 | 
					 | 
				
			||||||
	mode?: FloatingMode;
 | 
					 | 
				
			||||||
	dismissible?: boolean;
 | 
					 | 
				
			||||||
	role?: FloatRole;
 | 
					 | 
				
			||||||
	floatingOptions?: UseFloatingOptions;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A component to show something floating next to an element.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const Float = React.forwardRef(({children, content, className, mode, dismissible, role, floatingOptions}: FloatProperties, ref): React.ReactElement => {
 | 
					 | 
				
			||||||
	// By default, use "always" mode.
 | 
					 | 
				
			||||||
	if (!mode) mode = "always";
 | 
					 | 
				
			||||||
	if (dismissible === undefined && (mode != "always" && mode != "managed")) dismissible = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Followed show status.
 | 
					 | 
				
			||||||
	const [shown, setShown] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Create show / hide functions.
 | 
					 | 
				
			||||||
	const show = useCallback(() => setShown(true), [setShown]);
 | 
					 | 
				
			||||||
	const hide = useCallback(() => setShown(false), [setShown]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If show mode is "always", always show the floating part after render.
 | 
					 | 
				
			||||||
	if (mode == "always")
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		setTimeout(() => {
 | 
					 | 
				
			||||||
			setShown(true);
 | 
					 | 
				
			||||||
		}, 0);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Floating initialization.
 | 
					 | 
				
			||||||
	const { refs, floatingStyles, context, placement } = useFloating(
 | 
					 | 
				
			||||||
		useMemo(() => (Object.assign({
 | 
					 | 
				
			||||||
			open: shown,
 | 
					 | 
				
			||||||
			onOpenChange: setShown,
 | 
					 | 
				
			||||||
			middleware: [shift(), flip()],
 | 
					 | 
				
			||||||
		} as UseFloatingOptions, floatingOptions)), [floatingOptions, shown, setShown])
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Interactions initialization.
 | 
					 | 
				
			||||||
	const hover = useHover(context, useMemo(() => ({ enabled: mode == "hover" }), [mode]));
 | 
					 | 
				
			||||||
	const focus = useFocus(context, useMemo(() => ({ enabled: mode == "focus", visibleOnly: false }), [mode]));
 | 
					 | 
				
			||||||
	const click = useClick(context, useMemo(() => ({ enabled: mode == "click" }), [mode]));
 | 
					 | 
				
			||||||
	const dismiss = useDismiss(context, useMemo(() => ({ enabled: !!dismissible && mode != "click" }), [dismissible]));
 | 
					 | 
				
			||||||
	const roleProps = useRole(context, {
 | 
					 | 
				
			||||||
		role: role,
 | 
					 | 
				
			||||||
		enabled: !!role,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	const {getReferenceProps, getFloatingProps} = useInteractions([roleProps, dismiss, hover, focus, click]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Transition configuration.
 | 
					 | 
				
			||||||
	const {isMounted, styles: transitionStyles} = useTransitionStyles(context, {
 | 
					 | 
				
			||||||
		duration: 200,
 | 
					 | 
				
			||||||
		common: ({side}) => ({
 | 
					 | 
				
			||||||
			transformOrigin: {
 | 
					 | 
				
			||||||
				top: "bottom",
 | 
					 | 
				
			||||||
				bottom: "top",
 | 
					 | 
				
			||||||
				left: "right",
 | 
					 | 
				
			||||||
				right: "left",
 | 
					 | 
				
			||||||
			}[side],
 | 
					 | 
				
			||||||
		}),
 | 
					 | 
				
			||||||
		initial: {
 | 
					 | 
				
			||||||
			transform: "scale(0.66)",
 | 
					 | 
				
			||||||
			opacity: "0",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Change the child element to use the reference and the interactions properties.
 | 
					 | 
				
			||||||
	const referencedChild = useMemo(() => {
 | 
					 | 
				
			||||||
		// Render the children if a managed floating function is passed.
 | 
					 | 
				
			||||||
		const child = typeof children == "function" ? children(show, hide) : children;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return React.cloneElement(child,
 | 
					 | 
				
			||||||
			Object.assign(
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					// Pass references.
 | 
					 | 
				
			||||||
					ref: mergeRefs([ref, refs.setReference, child?.ref]),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				// Get interaction properties.
 | 
					 | 
				
			||||||
				getReferenceProps(),
 | 
					 | 
				
			||||||
			),
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	}, [children, show, hide, ref, refs.setReference, getReferenceProps]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update floating content.
 | 
					 | 
				
			||||||
	const floatingContent = useMemo(() => (
 | 
					 | 
				
			||||||
		// Render the children if a managed floating function is passed.
 | 
					 | 
				
			||||||
		typeof content == "function" ? content(show, hide) : content
 | 
					 | 
				
			||||||
	), [shown, show, hide, content]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<>
 | 
					 | 
				
			||||||
			{referencedChild}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			{ // Showing floating element if the state says to do so.
 | 
					 | 
				
			||||||
				isMounted &&
 | 
					 | 
				
			||||||
				<div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()} className={"floating"} data-placement={placement}>
 | 
					 | 
				
			||||||
					<Card style={transitionStyles} className={classes("floating", className)}>
 | 
					 | 
				
			||||||
						{floatingContent}
 | 
					 | 
				
			||||||
					</Card>
 | 
					 | 
				
			||||||
				</div>
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Float} from "./Float";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Tooltip({children, content}: {
 | 
					 | 
				
			||||||
	children: React.ReactElement;
 | 
					 | 
				
			||||||
	content: React.ReactNode;
 | 
					 | 
				
			||||||
}): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Float mode={"hover"} content={content} className={"tooltip"} role={"tooltip"} floatingOptions={{ placement: "top" }}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</Float>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Check} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Checkbox({children, className, type, ...inputProps}: React.PropsWithChildren<React.InputHTMLAttributes<HTMLInputElement>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("box", className)}>
 | 
					 | 
				
			||||||
			<input type={"checkbox"} {...inputProps} />
 | 
					 | 
				
			||||||
			<a className={"button"} tabIndex={-1}><Check /></a>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
import React, {useEffect, useRef} from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Custom validation rule in a form.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function CustomValidationRule({valid, errorMessage}: {
 | 
					 | 
				
			||||||
	valid: boolean;
 | 
					 | 
				
			||||||
	errorMessage: string;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// HTML virtual input ref.
 | 
					 | 
				
			||||||
	const ref = useRef<HTMLInputElement>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// When the validation is invalid, set a custom error message, set the custom error message.
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		ref.current.setCustomValidity(valid ? "" : errorMessage);
 | 
					 | 
				
			||||||
	}, [ref, valid, errorMessage]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<input ref={ref}
 | 
					 | 
				
			||||||
		       className={"virtual"} type={"text"} required={true}
 | 
					 | 
				
			||||||
		       value={valid ? "true" : ""} onChange={() => {}} />
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,155 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
 | 
					 | 
				
			||||||
import {classes, formatDate, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
import {Float} from "../Floating/Float";
 | 
					 | 
				
			||||||
import {Datepicker} from "../Dates/Datepicker";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A form input for a date with a datepicker.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function DatepickerInput(
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		children, className,
 | 
					 | 
				
			||||||
		value, onChange,
 | 
					 | 
				
			||||||
		// Properties to pass down.
 | 
					 | 
				
			||||||
		onKeyUp, onBlur,
 | 
					 | 
				
			||||||
		// Already set properties.
 | 
					 | 
				
			||||||
		type, placeholder,
 | 
					 | 
				
			||||||
		...props}: React.PropsWithChildren<Modify<React.InputHTMLAttributes<HTMLInputElement>, {
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * The current date value.
 | 
					 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
		value?: Date|null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * Called when picked date is changed.
 | 
					 | 
				
			||||||
		 * @param newDate The new date.
 | 
					 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
		onChange: (newDate: Date) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Already set properties.
 | 
					 | 
				
			||||||
		type?: never;
 | 
					 | 
				
			||||||
		placeholder?: never;
 | 
					 | 
				
			||||||
	}>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Date text state.
 | 
					 | 
				
			||||||
	const [dateText, setDateText] = useState("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update date text when date value has changed.
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		if (value && value instanceof Date && !isNaN(value.getTime()))
 | 
					 | 
				
			||||||
			setDateText(formatDate(value));
 | 
					 | 
				
			||||||
	}, [value]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Check if date is valid.
 | 
					 | 
				
			||||||
	const invalidDate = useMemo(() => !(value && value instanceof Date && !isNaN(value.getTime())) && dateText.length > 0, [value, dateText]);
 | 
					 | 
				
			||||||
	const dateValue = useMemo(() => invalidDate ? new Date() : (value ?? new Date()), [invalidDate, value]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const inputRef = useRef<HTMLInputElement>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Submit a new date from its raw text.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const submitDate = useCallback((customDateText?: string) => {
 | 
					 | 
				
			||||||
		// Get the current date text status.
 | 
					 | 
				
			||||||
		const dateTextMatch = (customDateText ?? dateText).match(/^([0-9]?[0-9])\/([0-9]?[0-9])\/([0-9]?[0-9]?[0-9]?[0-9])$/);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (dateTextMatch)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// Parse day.
 | 
					 | 
				
			||||||
			let day = dateTextMatch[1];
 | 
					 | 
				
			||||||
			if (day.length < 2) day = "0" + day;
 | 
					 | 
				
			||||||
			if (parseInt(day) <= 0) day = "01";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Parse month.
 | 
					 | 
				
			||||||
			let month = dateTextMatch[2];
 | 
					 | 
				
			||||||
			if (month.length < 2) month = "0" + month;
 | 
					 | 
				
			||||||
			if (parseInt(month) <= 0) month = "01";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Parse year.
 | 
					 | 
				
			||||||
			let year = parseInt(dateTextMatch[3]);
 | 
					 | 
				
			||||||
			if (year < 100)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				year += 1900;
 | 
					 | 
				
			||||||
				if ((new Date()).getFullYear() - year > 96) year += 100;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if (year < 1000) year += 1000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Try to build the structurally valid date.
 | 
					 | 
				
			||||||
			const date = new Date(year + "-" + month + "-" + day);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!isNaN(date.getTime()))
 | 
					 | 
				
			||||||
			{ // Date is valid, checking that it uses the right month (to fix the behavior when we go change 31/03 to 31/02, we want 28/02 or 29/02 and not 01/03).
 | 
					 | 
				
			||||||
				if ((date.getMonth() + 1) > parseInt(month))
 | 
					 | 
				
			||||||
				{ // Current date month is not valid, we're getting back to it.
 | 
					 | 
				
			||||||
					date.setDate(date.getDate() - 1);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if ((date.getMonth() + 1) < parseInt(month))
 | 
					 | 
				
			||||||
				{ // Current date month is not valid, we're getting back to it.
 | 
					 | 
				
			||||||
					date.setDate(date.getDate() + 1);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Try to keep original hours.
 | 
					 | 
				
			||||||
			date.setHours(value?.getHours() ?? 0, value?.getMinutes() ?? 0, value?.getSeconds() ?? 0, value?.getMilliseconds() ?? 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Set the structurally valid date.
 | 
					 | 
				
			||||||
			onChange?.(date);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
			// No structurally valid date, removing it.
 | 
					 | 
				
			||||||
			onChange?.(null);
 | 
					 | 
				
			||||||
	}, [dateText, onChange]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("datepicker-input", invalidDate ? "error" : null, className)}
 | 
					 | 
				
			||||||
			     // Keeping focus on the input when something else in the label takes it.
 | 
					 | 
				
			||||||
			     onFocus={useCallback(() => inputRef.current?.focus(), [inputRef])}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Float mode={"focus"} className={"datepicker"} content={
 | 
					 | 
				
			||||||
				<Datepicker date={dateValue} onDateSelected={onChange} />
 | 
					 | 
				
			||||||
			}>
 | 
					 | 
				
			||||||
				<input type={"text"} ref={inputRef} placeholder={"DD/MM/AAAA"} value={dateText}
 | 
					 | 
				
			||||||
				       onChange={useCallback((event) => {
 | 
					 | 
				
			||||||
					       let dateText = event.currentTarget.value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					       // Add first '/' if missing.
 | 
					 | 
				
			||||||
					       if (dateText.match(/^[0-9]{2}[^\/]*$/))
 | 
					 | 
				
			||||||
						       dateText = dateText.substring(0, 2) + "/" + dateText.substring(2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					       // Add second '/' if missing.
 | 
					 | 
				
			||||||
					       if (dateText.match(/^[0-9]{2}\/[0-9]{2}[^\/]*$/))
 | 
					 | 
				
			||||||
						       dateText = dateText.substring(0, 5) + "/" + dateText.substring(5);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					       // Set the new date text.
 | 
					 | 
				
			||||||
					       setDateText(dateText);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					       // If a full date has been entered, reading it.
 | 
					 | 
				
			||||||
					       const fullDateMatch = dateText.match(/^([0-9]{2})\/([0-9]{2})\/([0-9]{4})$/);
 | 
					 | 
				
			||||||
					       if (fullDateMatch)
 | 
					 | 
				
			||||||
					       { // We have a structurally valid date, submitting it.
 | 
					 | 
				
			||||||
						       submitDate(dateText);
 | 
					 | 
				
			||||||
					       }
 | 
					 | 
				
			||||||
				       }, [submitDate])}
 | 
					 | 
				
			||||||
				       onKeyUp={useCallback((event) => {
 | 
					 | 
				
			||||||
					       if (event.key == "Enter")
 | 
					 | 
				
			||||||
						       // Submit date when enter is pressed.
 | 
					 | 
				
			||||||
						       submitDate();
 | 
					 | 
				
			||||||
								 return onKeyUp?.(event);
 | 
					 | 
				
			||||||
				       }, [submitDate, onKeyUp])}
 | 
					 | 
				
			||||||
					     onBlur={useCallback((event) => {
 | 
					 | 
				
			||||||
						     // Submit date when leaving form input.
 | 
					 | 
				
			||||||
								 submitDate();
 | 
					 | 
				
			||||||
								 return onBlur?.(event);
 | 
					 | 
				
			||||||
							 }, [submitDate, onBlur])}
 | 
					 | 
				
			||||||
					     {...props} />
 | 
					 | 
				
			||||||
			</Float>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			{ // The date is invalid, showing a subtext to say so.
 | 
					 | 
				
			||||||
				invalidDate && (
 | 
					 | 
				
			||||||
					<span className={"error subtext"}>Invalid date.</span>
 | 
					 | 
				
			||||||
				)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
import React, {useState} from "react";
 | 
					 | 
				
			||||||
import {Eye, EyeSlash} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function PasswordInput({children, className, type, ...props}: React.PropsWithChildren<React.InputHTMLAttributes<HTMLInputElement>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const [showPassword, setShowPassword] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("password", className)}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
			<div>
 | 
					 | 
				
			||||||
				<input type={showPassword ? "text" : "password"} {...props} />
 | 
					 | 
				
			||||||
				<a className={"button"} tabIndex={-1} onClick={() => {
 | 
					 | 
				
			||||||
					setShowPassword(!showPassword);
 | 
					 | 
				
			||||||
				}}>
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						showPassword ? <EyeSlash /> : <Eye />
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				</a>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Check} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Radio({children, className, type, ...inputProps}: React.PropsWithChildren<React.InputHTMLAttributes<HTMLInputElement>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("box", className)}>
 | 
					 | 
				
			||||||
			<input type={"radio"} {...inputProps} />
 | 
					 | 
				
			||||||
			<a className={"button"} tabIndex={-1}><Check /></a>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function RequiredField({className, ...props}: React.HTMLAttributes<HTMLSpanElement>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return <span className={classes("required", className)} {...props}></span>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,117 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
 | 
					 | 
				
			||||||
import {classes, formatTime, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function TimepickerInput(
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		children, className,
 | 
					 | 
				
			||||||
		value, onChange,
 | 
					 | 
				
			||||||
		// Properties to pass down.
 | 
					 | 
				
			||||||
		onKeyUp, onBlur,
 | 
					 | 
				
			||||||
		// Already set properties.
 | 
					 | 
				
			||||||
		type, placeholder,
 | 
					 | 
				
			||||||
		...props
 | 
					 | 
				
			||||||
	}: React.PropsWithChildren<Modify<React.InputHTMLAttributes<HTMLInputElement>, {
 | 
					 | 
				
			||||||
		value?: Date|null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onChange: (newDateTime: Date) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Already set properties.
 | 
					 | 
				
			||||||
		type?: never;
 | 
					 | 
				
			||||||
		placeholder?: never;
 | 
					 | 
				
			||||||
	}>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Time text state.
 | 
					 | 
				
			||||||
	const [timeText, setTimeText] = useState("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update time text when datetime value has changed.
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		if (value && value instanceof Date && !isNaN(value.getTime()))
 | 
					 | 
				
			||||||
			setTimeText(formatTime(value));
 | 
					 | 
				
			||||||
	}, [value]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Check if time is valid.
 | 
					 | 
				
			||||||
	const invalidTime = useMemo(() => !(value && value instanceof Date && !isNaN(value.getTime())) && timeText.length > 0, [value, timeText]);
 | 
					 | 
				
			||||||
	const timeValue = useMemo(() => invalidTime ? new Date() : (value ?? new Date()), [invalidTime, value]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Submit a new time from its raw text.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const submitTime = useCallback((customTimeText?: string) => {
 | 
					 | 
				
			||||||
		// Get the current date text status.
 | 
					 | 
				
			||||||
		const timeTextMatch = (customTimeText ?? timeText).match(/^([0-2]?[0-9]):([0-5]?[0-9])$/);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (timeTextMatch)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// Parse hours.
 | 
					 | 
				
			||||||
			let rawHours = timeTextMatch[1];
 | 
					 | 
				
			||||||
			if (rawHours.length < 2) rawHours = "0" + rawHours;
 | 
					 | 
				
			||||||
			let hours = parseInt(rawHours);
 | 
					 | 
				
			||||||
			if (isNaN(hours)) hours = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Parse minutes.
 | 
					 | 
				
			||||||
			let rawMinutes = timeTextMatch[2];
 | 
					 | 
				
			||||||
			if (rawMinutes.length < 2) rawMinutes = "0" + rawMinutes;
 | 
					 | 
				
			||||||
			let minutes = parseInt(rawMinutes);
 | 
					 | 
				
			||||||
			if (isNaN(minutes)) minutes = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			//TODO
 | 
					 | 
				
			||||||
			// Parse seconds?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Try to build the structurally valid date with this time.
 | 
					 | 
				
			||||||
			//TODO
 | 
					 | 
				
			||||||
			const datetime = new Date(timeValue);
 | 
					 | 
				
			||||||
			datetime.setHours(hours, minutes);
 | 
					 | 
				
			||||||
			// Put the date back, if it changed to another day.
 | 
					 | 
				
			||||||
			datetime.setFullYear(timeValue.getFullYear(), timeValue.getMonth(), timeValue.getDate());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Set the structurally valid date.
 | 
					 | 
				
			||||||
			onChange?.(datetime);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
			// No structurally valid date, removing it.
 | 
					 | 
				
			||||||
			onChange?.(null);
 | 
					 | 
				
			||||||
	}, [timeText, timeValue, onChange]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("timepicker", className)}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<input type={"text"} placeholder={"HH:MM"} value={timeText}
 | 
					 | 
				
			||||||
			       onChange={useCallback((event) => {
 | 
					 | 
				
			||||||
				       let timeText = event.currentTarget.value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				       // Add first ':' if missing.
 | 
					 | 
				
			||||||
				       if (timeText.match(/^[0-9]{2}[^:]*$/))
 | 
					 | 
				
			||||||
					       timeText = timeText.substring(0, 2) + ":" + timeText.substring(2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				       // Set the new time text.
 | 
					 | 
				
			||||||
				       setTimeText(timeText);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				       // If a full time has been entered, reading it.
 | 
					 | 
				
			||||||
				       const fullTimeMatch = timeText.match(/^([0-2][0-9]):([0-5][0-9])$/);
 | 
					 | 
				
			||||||
				       if (fullTimeMatch)
 | 
					 | 
				
			||||||
				       { // We have a structurally valid time, submitting it.
 | 
					 | 
				
			||||||
					       submitTime(timeText);
 | 
					 | 
				
			||||||
				       }
 | 
					 | 
				
			||||||
			       }, [submitTime])}
 | 
					 | 
				
			||||||
			       onKeyUp={useCallback((event) => {
 | 
					 | 
				
			||||||
				       if (event.key == "Enter")
 | 
					 | 
				
			||||||
					       // Submit time when enter is pressed.
 | 
					 | 
				
			||||||
					       submitTime();
 | 
					 | 
				
			||||||
				       return onKeyUp?.(event);
 | 
					 | 
				
			||||||
			       }, [submitTime, onKeyUp])}
 | 
					 | 
				
			||||||
			       onBlur={useCallback((event) => {
 | 
					 | 
				
			||||||
				       // Submit time when leaving form input.
 | 
					 | 
				
			||||||
				       submitTime();
 | 
					 | 
				
			||||||
				       return onBlur?.(event);
 | 
					 | 
				
			||||||
			       }, [submitTime, onBlur])}
 | 
					 | 
				
			||||||
			       {...props} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			{ // The date is invalid, showing a subtext to say so.
 | 
					 | 
				
			||||||
				invalidTime && (
 | 
					 | 
				
			||||||
					<span className={"error subtext"}>Invalid time.</span>
 | 
					 | 
				
			||||||
				)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Check} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function ToggleSwitch({children, className, type, ...inputProps}: React.PropsWithChildren<React.InputHTMLAttributes<HTMLInputElement>>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("toggleswitch", className)}>
 | 
					 | 
				
			||||||
			<input type={"checkbox"} {...inputProps} />
 | 
					 | 
				
			||||||
			<a className={"button"} tabIndex={-1}><span className={"switch"}></span></a>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,10 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function GenericLoader({children}: React.PropsWithChildren<{}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={"generic loader"}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,37 +0,0 @@
 | 
				
			||||||
import React, {useCallback} from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function ListLoader({count, itemContent}: {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Sample items count.
 | 
					 | 
				
			||||||
	 * 3 by default.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	count?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Sample items content or content generator function.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	itemContent?: React.ReactNode|((key: number) => React.ReactNode);
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Sample items count. 3 by default.
 | 
					 | 
				
			||||||
	count = count === undefined ? 3 : count;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize the sample content generator.
 | 
					 | 
				
			||||||
	const contentGenerator = useCallback((key: number) => (
 | 
					 | 
				
			||||||
		typeof itemContent != "function"
 | 
					 | 
				
			||||||
			// No function given, return the given content or a sample text.
 | 
					 | 
				
			||||||
			? (itemContent ?? "Loading content...")
 | 
					 | 
				
			||||||
			// A function have been given, just return its result.
 | 
					 | 
				
			||||||
			: itemContent(key)
 | 
					 | 
				
			||||||
	), [itemContent]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<ul className={"list loader"}>
 | 
					 | 
				
			||||||
			{ // Render every sample item.
 | 
					 | 
				
			||||||
				[...Array(count)].map((_, key) => (
 | 
					 | 
				
			||||||
					<li key={key}><div className={"content"}>{contentGenerator(key)}</div></li>
 | 
					 | 
				
			||||||
				))
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</ul>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function SpinningLoader({ inline }: { inline?: boolean; })
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("spinning loader", inline ? "inline" : undefined)}></div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,55 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
import {IconContext} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {NavLink, NavLinkProps} from "react-router-dom";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main apps menu component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function AppsMenu({className, children, ...props}: React.HTMLAttributes<HTMLDivElement>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<IconContext.Consumer>
 | 
					 | 
				
			||||||
			{(value) => (
 | 
					 | 
				
			||||||
				<IconContext.Provider value={Object.assign({}, value, {
 | 
					 | 
				
			||||||
					size: 40,
 | 
					 | 
				
			||||||
					weight: "regular",
 | 
					 | 
				
			||||||
				})}>
 | 
					 | 
				
			||||||
					<nav className={classes("apps", "menu", className)} {...props}>
 | 
					 | 
				
			||||||
						<ul>
 | 
					 | 
				
			||||||
							{children}
 | 
					 | 
				
			||||||
						</ul>
 | 
					 | 
				
			||||||
					</nav>
 | 
					 | 
				
			||||||
				</IconContext.Provider>
 | 
					 | 
				
			||||||
			)}
 | 
					 | 
				
			||||||
		</IconContext.Consumer>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Component of an app item in apps menu.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function AppItem({className, children, ...props}: React.HTMLAttributes<HTMLAnchorElement>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<li>
 | 
					 | 
				
			||||||
			<a className={classes("app", "flat button", className)} {...props}>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</a>
 | 
					 | 
				
			||||||
		</li>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Component of an app link in apps menu.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function AppLink({className, children, ...props}: NavLinkProps & React.HTMLAttributes<HTMLAnchorElement>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<li>
 | 
					 | 
				
			||||||
			<NavLink className={classes("app", "flat button", className)} {...props}>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</NavLink>
 | 
					 | 
				
			||||||
		</li>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main component of a main menu.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function MainMenu({children, className, ...props}: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<nav className={classes("main", "menu", className)} {...props}>
 | 
					 | 
				
			||||||
			<ul>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
		</nav>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
import {SubmenuFloat} from "./SubmenuFloat";
 | 
					 | 
				
			||||||
import {NavLink, NavLinkProps} from "react-router-dom";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main menu item properties.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type MainMenuItemProperties = React.PropsWithChildren<Modify<React.AnchorHTMLAttributes<HTMLAnchorElement>, {
 | 
					 | 
				
			||||||
}>>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main component of a main menu item.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const MainMenuItem = React.forwardRef<HTMLAnchorElement, MainMenuItemProperties>(function SubmenuItem({children, ...props}: MainMenuItemProperties, ref)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<li>
 | 
					 | 
				
			||||||
			<a ref={ref} {...props}>{children}</a>
 | 
					 | 
				
			||||||
		</li>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A main menu item link properties.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type MainMenuLinkProperties = React.PropsWithChildren<Modify<NavLinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement>, {
 | 
					 | 
				
			||||||
}>>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A main menu item link.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const MainMenuLink = React.forwardRef<HTMLAnchorElement, MainMenuLinkProperties>(function MainMenuLink({children, ...props}: MainMenuLinkProperties, ref)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<li>
 | 
					 | 
				
			||||||
			<NavLink ref={ref} {...props}>{children}</NavLink>
 | 
					 | 
				
			||||||
		</li>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A main menu item that open a submenu.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function MainMenuItemSubmenu({submenu, className, children, ...props}: Modify<MainMenuItemProperties, {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The submenu content.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	submenu: React.ReactNode;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<SubmenuFloat submenu={submenu} role={"menu"} floatingOptions={{ placement: "bottom-start" }}>
 | 
					 | 
				
			||||||
			<MainMenuItem className={classes("submenu", className)} {...props}>{children}</MainMenuItem>
 | 
					 | 
				
			||||||
		</SubmenuFloat>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main component of a submenu.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Submenu({className, children, ...props}: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("submenu", className)} {...props}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,25 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Float, FloatProperties} from "../Floating/Float";
 | 
					 | 
				
			||||||
import {classes, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Add a submenu which opens on click on the child.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function SubmenuFloat({submenu, className, mode, dismissible, children, content, ...props}: Modify<FloatProperties, {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The submenu content.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	submenu: React.ReactNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Ignored overridden properties.
 | 
					 | 
				
			||||||
	content?: never;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Float mode={mode ?? "click"} dismissible={dismissible ?? true}
 | 
					 | 
				
			||||||
		       className={classes("submenu", className)}
 | 
					 | 
				
			||||||
		       content={submenu} {...props}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</Float>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,53 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
import {SubmenuFloat} from "./SubmenuFloat";
 | 
					 | 
				
			||||||
import {NavLink, NavLinkProps} from "react-router-dom";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Submenu item properties.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type SubmenuItemProperties = React.PropsWithChildren<Modify<React.AnchorHTMLAttributes<HTMLAnchorElement>, {
 | 
					 | 
				
			||||||
}>>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main component of a submenu item.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const SubmenuItem = React.forwardRef<HTMLAnchorElement, SubmenuItemProperties>(function SubmenuItem({className, children, ...props}: SubmenuItemProperties, ref)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<a ref={ref} className={classes("item", className)} {...props}>{children}</a>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A submenu item link properties.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type SubmenuLinkProperties = React.PropsWithChildren<Modify<NavLinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement>, {
 | 
					 | 
				
			||||||
}>>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A submenu item link.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const SubmenuLink = React.forwardRef<HTMLAnchorElement, SubmenuLinkProperties>(function SubmenuLink({className, children, ...props}: SubmenuLinkProperties, ref)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<NavLink ref={ref} className={classes("item", className)} {...props}>{children}</NavLink>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A submenu item that open a submenu.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function SubmenuItemSubmenu({submenu, className, children, ...props}: Modify<SubmenuItemProperties, {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The submenu content.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	submenu: React.ReactNode;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<SubmenuFloat submenu={submenu} role={"menu"} floatingOptions={{ placement: "right-start" }}>
 | 
					 | 
				
			||||||
			<SubmenuItem className={classes("submenu", className)} {...props}>{children}</SubmenuItem>
 | 
					 | 
				
			||||||
		</SubmenuFloat>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,68 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {X} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {useCurtains, useCallableCurtain} from "../Curtains/Curtains";
 | 
					 | 
				
			||||||
import {classes, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
import {ModalType, ModalTypeIcon} from "./ModalsTypes";
 | 
					 | 
				
			||||||
import {useCurtain} from "../Curtains/CurtainInstance";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 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>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,25 +0,0 @@
 | 
				
			||||||
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,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,41 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useContext} from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
import {NotificationContext, NotificationsContext} from "./Notifications";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Notifications types enumeration.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export enum NotificationType
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	NONE = "none",
 | 
					 | 
				
			||||||
	INFO = "info",
 | 
					 | 
				
			||||||
	SUCCESS = "success",
 | 
					 | 
				
			||||||
	WARNING = "warning",
 | 
					 | 
				
			||||||
	ERROR = "error",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Notification component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Notification({type, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	type?: NotificationType;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Default type is NONE.
 | 
					 | 
				
			||||||
	type = type ?? NotificationType.NONE;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get notifications context.
 | 
					 | 
				
			||||||
	const {close} = useContext(NotificationsContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get current notification UUID.
 | 
					 | 
				
			||||||
	const {uuid, closed} = useContext(NotificationContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize close notification function.
 | 
					 | 
				
			||||||
	const closeNotification = useCallback(() => { close(uuid); }, [uuid]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<li className={classes("notification", type, closed ? "closed" : undefined)} onMouseDown={closeNotification}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</li>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,217 +0,0 @@
 | 
				
			||||||
import React, {startTransition, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
 | 
					 | 
				
			||||||
import ReactDOM from "react-dom";
 | 
					 | 
				
			||||||
import {v4 as uuidv4} from "uuid";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Notification UUID type.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type NotificationUuid = string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Notification data.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface NotificationData
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The notification content.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	content: React.ReactNode;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type of notification emitter function.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type EmitNotificationFunction = (content: React.ReactNode) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type of notification close function.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type RemoveNotificationFunction = (uuid: NotificationUuid) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type of notification close function.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type CloseNotificationFunction = (uuid: NotificationUuid) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type of notification closed state function.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type IsNotificationClosedFunction = (uuid: NotificationUuid) => boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Interface of notifications state.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface NotificationsContextState
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Notification function.
 | 
					 | 
				
			||||||
	 * @param content Notification content.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	notify: EmitNotificationFunction;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Close notification function.
 | 
					 | 
				
			||||||
	 * @param uuid UUID of notification to close.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	close: CloseNotificationFunction;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Is given notification closed?
 | 
					 | 
				
			||||||
	 * @param uuid UUID of notification to get closed state.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	isClosed: IsNotificationClosedFunction;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const NotificationsContext = React.createContext<NotificationsContextState>({
 | 
					 | 
				
			||||||
	notify() {},
 | 
					 | 
				
			||||||
	close() {},
 | 
					 | 
				
			||||||
	isClosed() { return false; },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook to emit a notification.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useNotify(): EmitNotificationFunction
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return useContext(NotificationsContext).notify;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Notifications provider.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function NotificationsProvider({children}: React.PropsWithChildren<{}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Notifications.
 | 
					 | 
				
			||||||
	const [notifications, setNotifications] = useState<Record<NotificationUuid, NotificationData>>({});
 | 
					 | 
				
			||||||
	// Keeping track of closed notifications that are still on (while transitioning out).
 | 
					 | 
				
			||||||
	const [closedNotifications, setClosedNotifications] = useState<Record<NotificationUuid, boolean>>()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize remove notification function.
 | 
					 | 
				
			||||||
	const remove = useRef<RemoveNotificationFunction>();
 | 
					 | 
				
			||||||
	remove.current = useCallback((uuid) => {
 | 
					 | 
				
			||||||
		// Copy the notifications list.
 | 
					 | 
				
			||||||
		const newNotifications = {...notifications};
 | 
					 | 
				
			||||||
		const newClosedNotifications = {...closedNotifications};
 | 
					 | 
				
			||||||
		// Remove the given notification from the list.
 | 
					 | 
				
			||||||
		delete newNotifications[uuid];
 | 
					 | 
				
			||||||
		delete newClosedNotifications[uuid];
 | 
					 | 
				
			||||||
		// Set the new notifications list.
 | 
					 | 
				
			||||||
		setNotifications(newNotifications);
 | 
					 | 
				
			||||||
		setClosedNotifications(newClosedNotifications);
 | 
					 | 
				
			||||||
	}, [notifications, setNotifications, closedNotifications, setClosedNotifications]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize close notification function with animation.
 | 
					 | 
				
			||||||
	const close = useRef<CloseNotificationFunction>();
 | 
					 | 
				
			||||||
	close.current = useCallback((uuid) => {
 | 
					 | 
				
			||||||
		// Add the given curtain UUID to the list of closed curtains.
 | 
					 | 
				
			||||||
		setClosedNotifications({
 | 
					 | 
				
			||||||
			...closedNotifications,
 | 
					 | 
				
			||||||
			[uuid]: true,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Remove the notification 300ms later.
 | 
					 | 
				
			||||||
		window.setTimeout(() => {
 | 
					 | 
				
			||||||
			// Remove the curtain.
 | 
					 | 
				
			||||||
			remove.current(uuid);
 | 
					 | 
				
			||||||
		}, 300);
 | 
					 | 
				
			||||||
	}, [remove, closedNotifications, setClosedNotifications]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize isClosed notification function.
 | 
					 | 
				
			||||||
	const isClosed = useRef<IsNotificationClosedFunction>();
 | 
					 | 
				
			||||||
	isClosed.current = useCallback((uuid) => (!!closedNotifications?.[uuid]), [closedNotifications]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize notify function.
 | 
					 | 
				
			||||||
	const notify = useRef<EmitNotificationFunction>();
 | 
					 | 
				
			||||||
	notify.current = useCallback((content: React.ReactNode) => {
 | 
					 | 
				
			||||||
		// Generate a new notification UUID for the new notification.
 | 
					 | 
				
			||||||
		const notificationUuid = uuidv4();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Add the notification to the list of notifications, with the generated UUID.
 | 
					 | 
				
			||||||
		setNotifications({
 | 
					 | 
				
			||||||
			...notifications,
 | 
					 | 
				
			||||||
			[notificationUuid]: {
 | 
					 | 
				
			||||||
				content: content,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Close notification 10s later.
 | 
					 | 
				
			||||||
		setTimeout(() => {
 | 
					 | 
				
			||||||
			close.current(notificationUuid);
 | 
					 | 
				
			||||||
		}, 10000);
 | 
					 | 
				
			||||||
	}, [notifications, setNotifications]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize context state from action functions.
 | 
					 | 
				
			||||||
	const contextState = useMemo(() => ({
 | 
					 | 
				
			||||||
		notify: (content: React.ReactNode) => notify.current(content),
 | 
					 | 
				
			||||||
		close: (uuid: NotificationUuid) => close.current(uuid),
 | 
					 | 
				
			||||||
		isClosed: (uuid: NotificationUuid) => isClosed.current(uuid),
 | 
					 | 
				
			||||||
	}), [notify, isClosed]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<NotificationsContext.Provider value={contextState}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
			<NotificationsPortal notifications={notifications} />
 | 
					 | 
				
			||||||
		</NotificationsContext.Provider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Curtains portal manager.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function NotificationsPortal({notifications}: {
 | 
					 | 
				
			||||||
	notifications: Record<NotificationUuid, NotificationData>;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return ReactDOM.createPortal((
 | 
					 | 
				
			||||||
		<ul className={"notifications"}>
 | 
					 | 
				
			||||||
			{ // Show notifications list.
 | 
					 | 
				
			||||||
				Object.entries(notifications).map(([uuid, notificationData]) => (
 | 
					 | 
				
			||||||
					<NotificationInstance key={uuid} uuid={uuid}>
 | 
					 | 
				
			||||||
						{notificationData.content}
 | 
					 | 
				
			||||||
					</NotificationInstance>
 | 
					 | 
				
			||||||
				))
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</ul>
 | 
					 | 
				
			||||||
	), document.body);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A notification context.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const NotificationContext = React.createContext<{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Notification UUID.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	uuid: NotificationUuid;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Notification closed state.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	closed: boolean;
 | 
					 | 
				
			||||||
}>(undefined);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Notification component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function NotificationInstance({uuid, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Notification UUID.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	uuid: NotificationUuid;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get notifications context.
 | 
					 | 
				
			||||||
	const {isClosed} = useContext(NotificationsContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<NotificationContext.Provider value={{
 | 
					 | 
				
			||||||
			uuid: uuid,
 | 
					 | 
				
			||||||
			closed: isClosed(uuid),
 | 
					 | 
				
			||||||
		}}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</NotificationContext.Provider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,137 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useState} from "react";
 | 
					 | 
				
			||||||
import {Pagination} from "./Pagination";
 | 
					 | 
				
			||||||
import {SpinningLoader} from "../Loaders/SpinningLoader";
 | 
					 | 
				
			||||||
import {Await, useAsync} from "../../Async";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Paginated content component with custom page handling.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Paginate({ page, onChange, count, children }: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The current page.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	page: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Called when a new page is selected.
 | 
					 | 
				
			||||||
	 * @param newPage The newly selected page.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	onChange: (newPage: number) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Pages count.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	count: number;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Pagination page={page} onChange={onChange} count={count} />
 | 
					 | 
				
			||||||
		</>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Paginated content component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function AutoPaginate({ count, children }: {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Pages count.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	count: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Show the given page.
 | 
					 | 
				
			||||||
	 * @param page The page to show.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	children: (page: number) => React.ReactElement;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// The current page.
 | 
					 | 
				
			||||||
	const [page, setPage] = useState<number>(1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Paginate page={page} onChange={setPage} count={count}>
 | 
					 | 
				
			||||||
			{children(page)}
 | 
					 | 
				
			||||||
		</Paginate>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Asynchronous paginated content component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function AsyncPaginate<T>({ count, getData, children }: {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get pages count.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	count: () => Promise<number>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get data for the given page.
 | 
					 | 
				
			||||||
	 * @param page The page for which to get data.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getData: (page: number) => Promise<T>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Show the current page with its retrieved data.
 | 
					 | 
				
			||||||
	 * @param data Data of the page to show.
 | 
					 | 
				
			||||||
	 * @param page The page to show.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	children: (data: T, page: number) => React.ReactElement;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Getting pages count.
 | 
					 | 
				
			||||||
	const [asyncCount] = useAsync(count, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Await async={asyncCount} fallback={<SpinningLoader />}>
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				(count) => (
 | 
					 | 
				
			||||||
					<AutoPaginate count={count}>
 | 
					 | 
				
			||||||
						{(page) => <AsyncPage page={page} getData={getData} render={children} />}
 | 
					 | 
				
			||||||
					</AutoPaginate>
 | 
					 | 
				
			||||||
				)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</Await>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * An async page to render.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function AsyncPage<T>({page, getData, render}: {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The page number to show.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	page: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get data for the given page.
 | 
					 | 
				
			||||||
	 * @param page The page for which to get data.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getData: (page: number) => Promise<T>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Render the page with its retrieved data.
 | 
					 | 
				
			||||||
	 * @param data Data of the page to show.
 | 
					 | 
				
			||||||
	 * @param page The page to show.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	render: (data: T, page: number) => React.ReactElement;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Store function to get page data.
 | 
					 | 
				
			||||||
	const getPageData = useCallback(() => {
 | 
					 | 
				
			||||||
		return getData(page);
 | 
					 | 
				
			||||||
	}, [page]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Getting page data.
 | 
					 | 
				
			||||||
	const [asyncPageData] = useAsync(getPageData, [getPageData]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Await async={asyncPageData} fallback={<SpinningLoader />}>
 | 
					 | 
				
			||||||
			{(pageData) => render(pageData, page)}
 | 
					 | 
				
			||||||
		</Await>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,141 +0,0 @@
 | 
				
			||||||
import React, {useEffect, useState} from "react";
 | 
					 | 
				
			||||||
import {CaretLeft, CaretRight} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {Tooltip} from "../Floating/Tooltip";
 | 
					 | 
				
			||||||
import {usePreviousValue} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Pagination component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Pagination({ page, onChange, count }: {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The current page.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	page: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Called when a new page is selected.
 | 
					 | 
				
			||||||
	 * @param newPage The newly selected page.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	onChange: (newPage: number) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Pages count.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	count: number;
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Memorize previous page.
 | 
					 | 
				
			||||||
	const previousPage = usePreviousValue(page);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The input text to use, when the currently entered value is not a number.
 | 
					 | 
				
			||||||
	const [inputText, setInputText] = useState(undefined);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		if (page != previousPage)
 | 
					 | 
				
			||||||
			// If the page has changed, resetting the input text.
 | 
					 | 
				
			||||||
			setInputText(undefined);
 | 
					 | 
				
			||||||
	}, [previousPage, page]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<nav className={"pages"}>
 | 
					 | 
				
			||||||
			<Tooltip content={"Previous"}>
 | 
					 | 
				
			||||||
				<button className={"flat icon-only"} disabled={page <= 1} onClick={() => onChange(page - 1)}>
 | 
					 | 
				
			||||||
					<CaretLeft/>
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
			</Tooltip>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<ul>
 | 
					 | 
				
			||||||
				{ // First page, only show when the current page isn't the first page.
 | 
					 | 
				
			||||||
					page > 1 &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(1)}>1</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{ // Space between first page and shown pages.
 | 
					 | 
				
			||||||
					// Only show when the page isn't the first page.
 | 
					 | 
				
			||||||
					page - 4 > 1 &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={page - 4 > 2 ? undefined : () => onChange(page - 4)}>{page - 4 > 2 ? "···" : page - 4}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{ // Only show when the page isn't the first page.
 | 
					 | 
				
			||||||
					page - 3 > 1 &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(page - 3)}>{page - 3}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				{ // Only show when the page isn't the first page.
 | 
					 | 
				
			||||||
					page - 2 > 1 &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(page - 2)}>{page - 2}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				{ // Only show when the page isn't the first page.
 | 
					 | 
				
			||||||
					page - 1 > 1 &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(page - 1)}>{page - 1}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{ // Current page input.
 | 
					 | 
				
			||||||
					<li className={"current"}>
 | 
					 | 
				
			||||||
						<input type={"number"} step={1} min={1} max={count} value={inputText ?? page} onChange={(event) => {
 | 
					 | 
				
			||||||
							// Try to get the new value, between 1 and page count.
 | 
					 | 
				
			||||||
							const newValue = Math.max(Math.min(event.currentTarget.valueAsNumber, count), 1);
 | 
					 | 
				
			||||||
							if (isNaN(newValue))
 | 
					 | 
				
			||||||
								// Not a numeric value, keep the current input text without changing the page number.
 | 
					 | 
				
			||||||
								setInputText(event.currentTarget.value);
 | 
					 | 
				
			||||||
							else
 | 
					 | 
				
			||||||
							{ // The value has been read successfully, changing it and resetting input text.
 | 
					 | 
				
			||||||
								onChange(newValue);
 | 
					 | 
				
			||||||
								setInputText(undefined);
 | 
					 | 
				
			||||||
							}
 | 
					 | 
				
			||||||
						}} />
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{ // Only show when the page isn't the last page.
 | 
					 | 
				
			||||||
					page + 1 < count &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(page + 1)}>{page + 1}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				{ // Only show when the page isn't the last page.
 | 
					 | 
				
			||||||
					page + 2 < count &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(page + 2)}>{page + 2}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				{ // Only show when the page isn't the last page.
 | 
					 | 
				
			||||||
					page + 3 < count &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(page + 3)}>{page + 3}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{ // Space between shown pages and last page.
 | 
					 | 
				
			||||||
					// Only show when the page isn't the last page.
 | 
					 | 
				
			||||||
					page + 4 < count &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={page + 4 < (count - 1) ? undefined : () => onChange(page + 4)}>{page + 4 < (count - 1) ? "···" : page + 4}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				{ // Last page, only show when the current page isn't the last page.
 | 
					 | 
				
			||||||
					page < count &&
 | 
					 | 
				
			||||||
					<li>
 | 
					 | 
				
			||||||
						<button className={"flat"} onClick={() => onChange(count)}>{count}</button>
 | 
					 | 
				
			||||||
					</li>
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<Tooltip content={"Next"}>
 | 
					 | 
				
			||||||
				<button className={"flat icon-only"} disabled={page >= count} onClick={() => onChange(page + 1)}>
 | 
					 | 
				
			||||||
					<CaretRight/>
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
			</Tooltip>
 | 
					 | 
				
			||||||
		</nav>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,132 +0,0 @@
 | 
				
			||||||
import React, {MutableRefObject, useCallback, useMemo, useRef, useState} from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
import {Check} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Suggestions preselected options navigation configuration.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface SuggestionsNavigation
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Return true if the preselected options navigation is initialized.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	initialized(): boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Preselect the next option in the suggestions.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	next(): void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Preselect the previous option in the suggestions.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	previous(): void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Select the currently preselected option.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	select(): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook to get the preselected options navigation reference.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useSuggestionsNavigation(): MutableRefObject<SuggestionsNavigation>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return useRef({
 | 
					 | 
				
			||||||
		initialized(): boolean
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			return false;
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		next(): void
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		previous(): void
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		select(): void
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	} as SuggestionsNavigation);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function OptionsSuggestions<OptionKey extends keyof any, Option>({options, onSelected, selectedOptions, renderOption, navigator}: {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Options to suggest.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	options: Record<OptionKey, Option>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Called when an option is selected.
 | 
					 | 
				
			||||||
	 * @param key
 | 
					 | 
				
			||||||
	 * @param option
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	onSelected: (key: OptionKey, option: Option) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Already selected options that will be shown differently.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	selectedOptions?: Record<OptionKey, Option>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Render an option.
 | 
					 | 
				
			||||||
	 * @param option The option to render.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	renderOption?: (option: Option) => React.ReactNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * A reference to a preselected suggestions options navigation object.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	navigator?: MutableRefObject<SuggestionsNavigation>;
 | 
					 | 
				
			||||||
}): React.ReactNode
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Initialize default option render function.
 | 
					 | 
				
			||||||
	const defaultRenderOption = useCallback((option: Option) => (String(option)), []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const optionsArray = useMemo(() => (Object.entries(options) as [OptionKey, Option][]), [options]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const [preselectedOptionIndex, setPreselectedOptionIndex] = useState<number>(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const navigation = useMemo(() => ({
 | 
					 | 
				
			||||||
		initialized(): boolean
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		next(): void
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// Preselect the next option in the options array, or the first one if it's the last element.
 | 
					 | 
				
			||||||
			setPreselectedOptionIndex((optionsArray.length == (preselectedOptionIndex + 1)) ? 0 : (preselectedOptionIndex + 1));
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		previous(): void
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// Preselect the previous option in the options array, or the last one if it's the first element.
 | 
					 | 
				
			||||||
			setPreselectedOptionIndex(((preselectedOptionIndex - 1) < 0) ? (optionsArray.length - 1) : (preselectedOptionIndex - 1));
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		select(): void
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// Get the currently preselected option.
 | 
					 | 
				
			||||||
			const [optionKey, option] = optionsArray[preselectedOptionIndex];
 | 
					 | 
				
			||||||
			// Select the currently preselected option.
 | 
					 | 
				
			||||||
			onSelected(optionKey, option);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}), [optionsArray, preselectedOptionIndex, setPreselectedOptionIndex]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (navigator)
 | 
					 | 
				
			||||||
		// If navigator reference is set, assigning it.
 | 
					 | 
				
			||||||
		navigator.current = navigation;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return optionsArray.map(([key, option], index) => (
 | 
					 | 
				
			||||||
		<a key={String(key)} className={classes("suggestion", preselectedOptionIndex == index ? "preselected" : null, selectedOptions?.[key] ? "selected" : null)}
 | 
					 | 
				
			||||||
		   onClick={() => { onSelected(key, option); }}>
 | 
					 | 
				
			||||||
			{(renderOption ?? defaultRenderOption)(option)}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<span className={"selected"}><Check /></span>
 | 
					 | 
				
			||||||
		</a>
 | 
					 | 
				
			||||||
	));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,250 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useMemo, useRef} from "react";
 | 
					 | 
				
			||||||
import {Suggestible} from "./Suggestible";
 | 
					 | 
				
			||||||
import {OptionsSuggestions, useSuggestionsNavigation} from "./OptionsSuggestions";
 | 
					 | 
				
			||||||
import {classes, Modify, normalizeString} from "../../Utils";
 | 
					 | 
				
			||||||
import {CaretDown, Check, X} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {CustomValidationRule} from "../Forms/CustomValidationRule";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Generic select component properties.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type SelectProperties<OptionKey extends keyof any, Option> = React.PropsWithChildren<Modify<React.InputHTMLAttributes<HTMLInputElement>, {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The currently selected option(s).
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	value: OptionKey|OptionKey[];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Called when new options are selected.
 | 
					 | 
				
			||||||
	 * @param newValue
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	onChange: (newValue: OptionKey|OptionKey[]) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Options list or a way to get it from a given search.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	options: Record<OptionKey, Option>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Render an option.
 | 
					 | 
				
			||||||
	 * @param option The option to render.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	renderOption?: (option: Option) => React.ReactNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Option match function. Return true if the given option matches the given search query.
 | 
					 | 
				
			||||||
	 * @param search The search query.
 | 
					 | 
				
			||||||
	 * @param option The option that should match the search query.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	match?: (search: string, option: Option) => boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Min count of options to allow to select.
 | 
					 | 
				
			||||||
	 * 0 by default, 1 when required is true.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	selectibleMinCount?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Max count of options to allow to select.
 | 
					 | 
				
			||||||
	 * 1 by default when multiple is false, infinity when multiple is true.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	selectibleMaxCount?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * True to allow to select an infinite count of options.
 | 
					 | 
				
			||||||
	 * false by default.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	multiple?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * When true, the select loses focus when a new option is selected (by clicking or pressing Enter).
 | 
					 | 
				
			||||||
	 * false by default.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	blurOnSelect?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * When true, the select loses focus when the max count of selectible options have been reached (by clicking or pressing Enter).
 | 
					 | 
				
			||||||
	 * true by default.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	blurWhenMaxCountSelected?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Already set properties.
 | 
					 | 
				
			||||||
	type?: never;
 | 
					 | 
				
			||||||
	role?: never;
 | 
					 | 
				
			||||||
}>>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Generic select component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Select<OptionKey extends keyof any, Option>(
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		className,
 | 
					 | 
				
			||||||
		value, onChange,
 | 
					 | 
				
			||||||
		options, renderOption, match,
 | 
					 | 
				
			||||||
		required, selectibleMinCount,
 | 
					 | 
				
			||||||
		selectibleMaxCount, multiple,
 | 
					 | 
				
			||||||
		blurOnSelect, blurWhenMaxCountSelected,
 | 
					 | 
				
			||||||
		// Properties to pass down.
 | 
					 | 
				
			||||||
		onKeyDown,
 | 
					 | 
				
			||||||
		// Already set properties.
 | 
					 | 
				
			||||||
		type, role,
 | 
					 | 
				
			||||||
		children,
 | 
					 | 
				
			||||||
		...props
 | 
					 | 
				
			||||||
	}: SelectProperties<OptionKey, Option>): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const [search, setSearch] = React.useState("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// By default, allow to select only one option.
 | 
					 | 
				
			||||||
	// If `multiple` is set and `selectibleMaxCount` is not, allow an infinite count of options to select.
 | 
					 | 
				
			||||||
	selectibleMaxCount = selectibleMaxCount ?? ((multiple === undefined ? false : multiple) ? Infinity : 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// By default, allow to select no option.
 | 
					 | 
				
			||||||
	// If `required` is set, `selectibleMinCount` will be set as 1 by default.
 | 
					 | 
				
			||||||
	selectibleMinCount = selectibleMinCount ?? (required ? 1 : 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// true by default.
 | 
					 | 
				
			||||||
	blurOnSelect = blurOnSelect === undefined ? false : blurOnSelect;
 | 
					 | 
				
			||||||
	blurWhenMaxCountSelected = blurWhenMaxCountSelected === undefined ? true : blurWhenMaxCountSelected;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize default option render function.
 | 
					 | 
				
			||||||
	const defaultRenderOption = useCallback((option: Option) => (String(option)), []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize default match option function.
 | 
					 | 
				
			||||||
	const defaultMatchOption = useCallback((search: string, option: Option) => normalizeString(String(option)).includes(normalizeString(search)), []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// An array of the selected options.
 | 
					 | 
				
			||||||
	const selectedOptions = useMemo(() => (
 | 
					 | 
				
			||||||
		// Normalize value to an array.
 | 
					 | 
				
			||||||
		((!Array.isArray(value) ? [value] : value)
 | 
					 | 
				
			||||||
			// Try to get the corresponding option for each given value.
 | 
					 | 
				
			||||||
			.map((optionKey) => [optionKey, options?.[optionKey]]) as [OptionKey, Option][])
 | 
					 | 
				
			||||||
			// Filter non-existing options.
 | 
					 | 
				
			||||||
			.filter(([_, option]) => (option !== undefined))
 | 
					 | 
				
			||||||
	), [value, options]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// A reference to the main search input.
 | 
					 | 
				
			||||||
	const inputRef = useRef<HTMLInputElement>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The suggestions' navigator.
 | 
					 | 
				
			||||||
	const suggestionsNavigator = useSuggestionsNavigation();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get all available options, filtered using search query.
 | 
					 | 
				
			||||||
	const filteredOptions = useMemo(() => (
 | 
					 | 
				
			||||||
		!search || (search.length == 0)
 | 
					 | 
				
			||||||
			// Nothing is searched, return all options.
 | 
					 | 
				
			||||||
			? options
 | 
					 | 
				
			||||||
			// Filter options using search query and matching function.
 | 
					 | 
				
			||||||
			: Object.fromEntries(
 | 
					 | 
				
			||||||
				(Object.entries(options) as [OptionKey, Option][]).filter(([optionKey, option]) => (
 | 
					 | 
				
			||||||
					(match ?? defaultMatchOption)(search, option)
 | 
					 | 
				
			||||||
				))
 | 
					 | 
				
			||||||
			) as Record<OptionKey, Option>
 | 
					 | 
				
			||||||
	), [options, search, match, defaultMatchOption]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Called when a new option is selected.
 | 
					 | 
				
			||||||
	const handleSelectedOption = useCallback((selectedOption: OptionKey) => {
 | 
					 | 
				
			||||||
		// Get an associative object from selected options.
 | 
					 | 
				
			||||||
		const currentSelection = Object.fromEntries(selectedOptions) as Record<OptionKey, Option>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Initialize the new selection variable.
 | 
					 | 
				
			||||||
		let newSelection: OptionKey|OptionKey[] = Object.keys(currentSelection) as OptionKey[];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (selectibleMaxCount == 1)
 | 
					 | 
				
			||||||
			// Only one possible selection = the newly selected option is the new selection.
 | 
					 | 
				
			||||||
			newSelection = selectedOption;
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
		{ // Multiple selections possible.
 | 
					 | 
				
			||||||
			if (!currentSelection[selectedOption])
 | 
					 | 
				
			||||||
			{ // The newly selected option wasn't selected, we should add it in the selection array.
 | 
					 | 
				
			||||||
				// Add the newly selected option in the array.
 | 
					 | 
				
			||||||
				newSelection = [...newSelection, selectedOption];
 | 
					 | 
				
			||||||
				if (newSelection.length > selectibleMaxCount)
 | 
					 | 
				
			||||||
					// If the array is now too big, we remove the first options to match the max count of options.
 | 
					 | 
				
			||||||
					newSelection.splice(0, newSelection.length - selectibleMaxCount);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			else
 | 
					 | 
				
			||||||
			{ // The option was already selected, we should deselect it.
 | 
					 | 
				
			||||||
				newSelection = newSelection.filter((key) => key != selectedOption);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Call onChange event with the new selection.
 | 
					 | 
				
			||||||
		onChange(newSelection);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Reset search query.
 | 
					 | 
				
			||||||
		setSearch("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (
 | 
					 | 
				
			||||||
			// Always blur on selection.
 | 
					 | 
				
			||||||
			blurOnSelect
 | 
					 | 
				
			||||||
			||
 | 
					 | 
				
			||||||
			// Blur when the new selection now reach the max selectible count.
 | 
					 | 
				
			||||||
			(blurWhenMaxCountSelected && ((selectibleMaxCount == 1 && !!newSelection) || (selectibleMaxCount == (newSelection as OptionKey[])?.length)))
 | 
					 | 
				
			||||||
		) // Try to lose focus, as a new selection has been made and blur conditions are reached.
 | 
					 | 
				
			||||||
			window.setTimeout(() => { inputRef?.current?.blur(); }, 0);
 | 
					 | 
				
			||||||
	}, [selectedOptions, onChange, setSearch]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Called when an option is deselected.
 | 
					 | 
				
			||||||
	const handleDeselectedOption = useCallback((deselectedOption: OptionKey) => {
 | 
					 | 
				
			||||||
		// Call onChange event with the new selection.
 | 
					 | 
				
			||||||
		onChange(
 | 
					 | 
				
			||||||
			selectedOptions
 | 
					 | 
				
			||||||
				// Remove deselected option if it was in the selected options array.
 | 
					 | 
				
			||||||
				.filter(([optionKey]) => (optionKey != deselectedOption))
 | 
					 | 
				
			||||||
				.map(([optionKey]) => optionKey)
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	}, [selectedOptions, onChange]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<label className={classes("select", className)}
 | 
					 | 
				
			||||||
		       onFocus={useCallback(() => {
 | 
					 | 
				
			||||||
			       inputRef.current?.focus();
 | 
					 | 
				
			||||||
		       }, [inputRef])}>
 | 
					 | 
				
			||||||
			<div>
 | 
					 | 
				
			||||||
				<Suggestible suggestions={
 | 
					 | 
				
			||||||
					<OptionsSuggestions options={filteredOptions} renderOption={renderOption}
 | 
					 | 
				
			||||||
						                  // Get an associative object from selected options.
 | 
					 | 
				
			||||||
					                    selectedOptions={Object.fromEntries(selectedOptions) as Record<OptionKey, Option>}
 | 
					 | 
				
			||||||
					                    onSelected={handleSelectedOption} navigator={suggestionsNavigator}/>
 | 
					 | 
				
			||||||
				}>
 | 
					 | 
				
			||||||
					<input ref={inputRef} type={"text"} role={"select"} value={search}
 | 
					 | 
				
			||||||
					       onKeyDown={(event) => {
 | 
					 | 
				
			||||||
						       if (event.key == "ArrowDown")
 | 
					 | 
				
			||||||
							       suggestionsNavigator.current?.next();
 | 
					 | 
				
			||||||
						       else if (event.key == "ArrowUp")
 | 
					 | 
				
			||||||
							       suggestionsNavigator.current?.previous();
 | 
					 | 
				
			||||||
						       else if (event.key == "Enter")
 | 
					 | 
				
			||||||
							       suggestionsNavigator.current?.select();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						       return onKeyDown?.(event);
 | 
					 | 
				
			||||||
					       }}
 | 
					 | 
				
			||||||
					       onChange={(event) => setSearch(event.currentTarget.value)}
 | 
					 | 
				
			||||||
					       {...props} />
 | 
					 | 
				
			||||||
				</Suggestible>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<a className={"button"} tabIndex={-1}><CaretDown /></a>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<CustomValidationRule valid={selectedOptions.length >= selectibleMinCount}
 | 
					 | 
				
			||||||
			                      errorMessage={`At least ${selectibleMinCount} option${selectibleMinCount > 1 ? "s are" : " is"} required.`} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<ul className={"selected"}>
 | 
					 | 
				
			||||||
				{ // Showing each selected value.
 | 
					 | 
				
			||||||
					selectedOptions.map(([optionKey, option]) => (
 | 
					 | 
				
			||||||
						<li key={String(optionKey)}>
 | 
					 | 
				
			||||||
							<Check />
 | 
					 | 
				
			||||||
							<div className={"option"}>{(renderOption ?? defaultRenderOption)(option)}</div>
 | 
					 | 
				
			||||||
							<button className={"remove flat"} type={"button"} onMouseDown={() => handleDeselectedOption(optionKey)}>
 | 
					 | 
				
			||||||
								<X />
 | 
					 | 
				
			||||||
							</button>
 | 
					 | 
				
			||||||
						</li>
 | 
					 | 
				
			||||||
					))
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<div className={"label"}>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
		</label>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function SimpleSuggestions(): React.ReactElement
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<>
 | 
					 | 
				
			||||||
			<a className={"suggestion"}>test</a>
 | 
					 | 
				
			||||||
			<a className={"suggestion"}>another</a>
 | 
					 | 
				
			||||||
		</>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,23 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {Float, FloatProperties} from "../Floating/Float";
 | 
					 | 
				
			||||||
import {classes, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Suggestible({className, suggestions, mode, content, role, children, ...props}: Modify<FloatProperties, {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Suggestions element.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	suggestions: React.ReactNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	content?: never;
 | 
					 | 
				
			||||||
	role?: never;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Default mode for showing suggestions is "focus".
 | 
					 | 
				
			||||||
	mode = mode ?? "focus";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<Float className={classes("suggestions", className)} role={"select"} dismissible={false} content={suggestions} mode={mode} {...props}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</Float>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,109 +0,0 @@
 | 
				
			||||||
import React, {useEffect} from "react";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
	GlobalStateProvider,
 | 
					 | 
				
			||||||
	useGlobalStateReducers, useGlobalStateValue,
 | 
					 | 
				
			||||||
} from "../../GlobalState";
 | 
					 | 
				
			||||||
import {usePreviousValue} from "../../Utils";
 | 
					 | 
				
			||||||
import {StepKeyType, stepsGlobalState, useCurrentStepKey, useStepsNavigator} from "./StepsContext";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main Steps component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Steps({children}: React.PropsWithChildren<{}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<GlobalStateProvider globalState={stepsGlobalState}>
 | 
					 | 
				
			||||||
			<div className={"steps"}>
 | 
					 | 
				
			||||||
				<StepsNavigatorComponent />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<div>
 | 
					 | 
				
			||||||
					{children}
 | 
					 | 
				
			||||||
				</div>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
		</GlobalStateProvider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Steps navigator component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function StepsNavigatorComponent()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the steps navigator functions.
 | 
					 | 
				
			||||||
	const stepsNavigator = useStepsNavigator();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get the current steps state.
 | 
					 | 
				
			||||||
	const stepsState = useGlobalStateValue(stepsGlobalState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get the current step.
 | 
					 | 
				
			||||||
	const currentStep = useCurrentStepKey();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<nav className={"steps"}>
 | 
					 | 
				
			||||||
			<ul>
 | 
					 | 
				
			||||||
				{ // Showing a button for each step.
 | 
					 | 
				
			||||||
					stepsState.steps.map((step, index) => (
 | 
					 | 
				
			||||||
						// Rendering the current step button.
 | 
					 | 
				
			||||||
						<li key={step.key} className={currentStep == step.key ? "active" : undefined}>
 | 
					 | 
				
			||||||
							<button type={"button"} onClick={() => {
 | 
					 | 
				
			||||||
								stepsNavigator.set(step.key);
 | 
					 | 
				
			||||||
							}}>
 | 
					 | 
				
			||||||
								{step.title ?? (index + 1)}
 | 
					 | 
				
			||||||
							</button>
 | 
					 | 
				
			||||||
						</li>
 | 
					 | 
				
			||||||
					))
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
		</nav>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Component of a step.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Step({stepKey, stepTitle, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The current step unique key.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	stepKey: StepKeyType;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The step title, to show in the navigator.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	stepTitle?: React.ReactNode;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the global state reducers class.
 | 
					 | 
				
			||||||
	const stepsGlobalStateReducers = useGlobalStateReducers(stepsGlobalState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get the previous step key.
 | 
					 | 
				
			||||||
	const previousStepKey = usePreviousValue(stepKey);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		// Remove the previous step key, if there is one.
 | 
					 | 
				
			||||||
		if (previousStepKey) stepsGlobalStateReducers.removeStep(previousStepKey);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Register the current step key.
 | 
					 | 
				
			||||||
		stepsGlobalStateReducers.registerStep(stepKey, stepTitle);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Remove the step key when component is removed.
 | 
					 | 
				
			||||||
		return () => stepsGlobalStateReducers.removeStep(stepKey);
 | 
					 | 
				
			||||||
	}, [stepsGlobalStateReducers, previousStepKey, stepTitle]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get the current step key.
 | 
					 | 
				
			||||||
	const currentStep = useCurrentStepKey();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (currentStep != stepKey)
 | 
					 | 
				
			||||||
		// If this step is not the current one, rendering nothing.
 | 
					 | 
				
			||||||
		return undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Rendering the current step.
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<section className={"step"}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,165 +0,0 @@
 | 
				
			||||||
import React, {useMemo} from "react";
 | 
					 | 
				
			||||||
import {GlobalState, GlobalStateReducers, useGlobalStateReducers, useGlobalStateValue} from "../../GlobalState";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type of step key.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type StepKeyType = string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Steps global state data type.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface StepsGlobalStateType
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Steps list, by their order of apparition.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	steps: { key: StepKeyType; title?: React.ReactNode; }[];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The index of the current step in the steps list.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	currentStepIndex: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The steps global state modification functions.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class StepsGlobalStateReducers extends GlobalStateReducers<StepsGlobalStateType>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Register a new step with its step key.
 | 
					 | 
				
			||||||
	 * @param newStepKey The step key to register.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	registerStep(newStepKey: StepKeyType, newStepTitle?: React.ReactNode): void
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		this.setState({
 | 
					 | 
				
			||||||
			// Add the given step key to the steps array, ensuring that it will be there only once.
 | 
					 | 
				
			||||||
			steps: [...this.state.steps.filter(({key: currentStepKey}) => currentStepKey != newStepKey), { key: newStepKey, title: newStepTitle }]
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Remove an old step with its step key.
 | 
					 | 
				
			||||||
	 * @param oldStepKey The step key to remove.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	removeStep(oldStepKey: StepKeyType): void
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		this.setState({
 | 
					 | 
				
			||||||
			// Remove the given step key from the steps array.
 | 
					 | 
				
			||||||
			steps: this.state.steps.filter(({key: currentStepKey}) => currentStepKey != oldStepKey)
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Set the current step from its key.
 | 
					 | 
				
			||||||
	 * @param stepKey The step key to set as the current one.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	setCurrentStep(stepKey: StepKeyType): void
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// Get the new step index.
 | 
					 | 
				
			||||||
		const stepIndex = this.state.steps.findIndex(({key: currentStep}) => currentStep == stepKey);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (stepIndex >= 0)
 | 
					 | 
				
			||||||
		{ // If the new step index has been found, setting it as the current one.
 | 
					 | 
				
			||||||
			this.setState({
 | 
					 | 
				
			||||||
				currentStepIndex: stepIndex,
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get the next step key.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getNextStep(): StepKeyType
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return this.state.steps[this.state.currentStepIndex + 1]?.key ?? this.state.steps[0]?.key;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get the previous step key.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getPreviousStep(): StepKeyType
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return this.state.steps[this.state.currentStepIndex - 1]?.key ?? this.state.steps[this.state.steps.length - 1]?.key;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The steps global state.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const stepsGlobalState = new GlobalState<StepsGlobalStateType, StepsGlobalStateReducers>({
 | 
					 | 
				
			||||||
	steps: [],
 | 
					 | 
				
			||||||
	currentStepIndex: 0,
 | 
					 | 
				
			||||||
}, new StepsGlobalStateReducers);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook of the current step key in a steps component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useCurrentStepKey(): StepKeyType
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the current steps global state value.
 | 
					 | 
				
			||||||
	const stepsState = useGlobalStateValue(stepsGlobalState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get the current step from the global state value.
 | 
					 | 
				
			||||||
	return stepsState.steps.length > 0
 | 
					 | 
				
			||||||
		? ( // If there are steps, getting the current one.
 | 
					 | 
				
			||||||
			stepsState.currentStepIndex < stepsState.steps.length
 | 
					 | 
				
			||||||
				? ( // If the index is correctly defined, trying to get the corresponding state key
 | 
					 | 
				
			||||||
					stepsState.currentStepIndex >= 0 ? (stepsState.steps[stepsState.currentStepIndex]?.key) : stepsState.steps[0]?.key
 | 
					 | 
				
			||||||
				)
 | 
					 | 
				
			||||||
				// The current index is too high, taking the last step as the current one.
 | 
					 | 
				
			||||||
				: stepsState.steps[stepsState.steps.length - 1]?.key
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		// There are no steps, returning none.
 | 
					 | 
				
			||||||
		: undefined;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Steps navigator
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface StepsNavigator
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Go to the next step.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	next(): void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Return to the previous step.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	previous(): void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Go to the given step.
 | 
					 | 
				
			||||||
	 * @param stepKey The step key to join.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	set(stepKey: StepKeyType): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Hook of the steps' navigator.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useStepsNavigator(): StepsNavigator
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the steps reducers instance.
 | 
					 | 
				
			||||||
	const stepsReducers = useGlobalStateReducers(stepsGlobalState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Create the steps navigator object.
 | 
					 | 
				
			||||||
	return useMemo(() => ({
 | 
					 | 
				
			||||||
		next()
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			stepsReducers.setCurrentStep(stepsReducers.getNextStep());
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		previous()
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			stepsReducers.setCurrentStep(stepsReducers.getPreviousStep());
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		set(stepKey: StepKeyType)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			stepsReducers.setCurrentStep(stepKey);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}), [stepsReducers]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,59 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {X} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
import {useCallableCurtain, useCurtains} from "../Curtains/Curtains";
 | 
					 | 
				
			||||||
import {classes, Modify} from "../../Utils";
 | 
					 | 
				
			||||||
import {useCurtain} from "../Curtains/CurtainInstance";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * More natural name of useCurtains for subapps.
 | 
					 | 
				
			||||||
 * @see useCurtains
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const useSubapps = useCurtains;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * More natural name of useCurtain for subapps.
 | 
					 | 
				
			||||||
 * @see useCurtain
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const useSubapp = useCurtain;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * More natural name of useCallableCurtain for subapps.
 | 
					 | 
				
			||||||
 * @see useCallableCurtain
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const useCallableSubapp = useCallableCurtain;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Subapp main component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Subapp({className, title, closable, children}: React.PropsWithChildren<Modify<React.HTMLAttributes<HTMLDivElement>, {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Subapp title.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	title?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Can disable close button.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	closable?: boolean;
 | 
					 | 
				
			||||||
}>>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Subapp is closable by default.
 | 
					 | 
				
			||||||
	closable = closable !== undefined ? closable : true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Subapp state.
 | 
					 | 
				
			||||||
	const {close} = useSubapp();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<div className={classes("subapp", className)}>
 | 
					 | 
				
			||||||
			<header>
 | 
					 | 
				
			||||||
				<h1>{title ?? ""}</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				<button className={"icon-only close"} onClick={close} disabled={!closable}><X size={32}/></button>
 | 
					 | 
				
			||||||
			</header>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			<main>
 | 
					 | 
				
			||||||
				{children}
 | 
					 | 
				
			||||||
			</main>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,17 +0,0 @@
 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
import {classes} from "../../Utils";
 | 
					 | 
				
			||||||
import {Info} from "@phosphor-icons/react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Simple text tip component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function Tip({className, children, ...props}: React.PropsWithChildren<React.HTMLAttributes<HTMLParagraphElement>>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<p className={classes("tip", className)} {...props}>
 | 
					 | 
				
			||||||
			<Info weight={"duotone"} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</p>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,184 +0,0 @@
 | 
				
			||||||
import React, {PropsWithChildren, useCallback, useContext, useState} from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Global state modifiers functions.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class GlobalStateReducers<S>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The current state (readonly).
 | 
					 | 
				
			||||||
	 * @protected
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	protected state: Readonly<S>;
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * State update system.
 | 
					 | 
				
			||||||
	 * @private
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	private stateUpdater: React.Dispatch<S>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Assign changes to the current state.
 | 
					 | 
				
			||||||
	 * @param stateUpdate Changes to apply to the current state.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	setState(stateUpdate: Partial<S>)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// Apply update object to the current state.
 | 
					 | 
				
			||||||
		this.state = Object.assign({}, this.state, stateUpdate);
 | 
					 | 
				
			||||||
		// Update the changed state.
 | 
					 | 
				
			||||||
		this.stateUpdater(this.state);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Reducers definition interface for easy type definition.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
interface ReducerHack<S>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * The current state (readonly).
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	state: Readonly<S>;
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * State update system.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	stateUpdater: React.Dispatch<S>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Main global state class.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class GlobalState<S, R extends GlobalStateReducers<S>>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Global state context.
 | 
					 | 
				
			||||||
	 * @protected
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	protected context: React.Context<S>;
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * State update system, used in the reducers class.
 | 
					 | 
				
			||||||
	 * @protected
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	protected stateUpdater: React.Dispatch<S>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Create a new global state.
 | 
					 | 
				
			||||||
	 * @param defaultValue The default value of the global state.
 | 
					 | 
				
			||||||
	 * @param reducers Reducers class.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	constructor(protected defaultValue: S, protected reducers: R)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// Create a new context for the global state.
 | 
					 | 
				
			||||||
		this.context = React.createContext<S>(defaultValue);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get the context for the global state.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getContext(): React.Context<S>
 | 
					 | 
				
			||||||
	{ return this.context; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get the default value of the global state.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getDefaultValue(): S
 | 
					 | 
				
			||||||
	{ return this.defaultValue; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Assign the state update system, used in the reducers class.
 | 
					 | 
				
			||||||
	 * @param updater State update system instance.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	setStateUpdater(updater: React.Dispatch<S>): void
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		this.stateUpdater = updater;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get the configured state reducers class for the current state.
 | 
					 | 
				
			||||||
	 * @param state The state on which the reducers should apply.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	getStateReducers(state: S): R
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// Assign the given state.
 | 
					 | 
				
			||||||
		(this.reducers as unknown as ReducerHack<S>).state = state;
 | 
					 | 
				
			||||||
		// Assign the current state updater.
 | 
					 | 
				
			||||||
		(this.reducers as unknown as ReducerHack<S>).stateUpdater = this.stateUpdater;
 | 
					 | 
				
			||||||
		return this.reducers;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Global state Provider hook.
 | 
					 | 
				
			||||||
 * @param globalState Global state for which to get the global state Provider.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useGlobalStateProvider<S, R extends GlobalStateReducers<S>>(globalState: GlobalState<S, R>): React.FunctionComponent<PropsWithChildren<{}>>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the provider from the given global state context.
 | 
					 | 
				
			||||||
	const Provider = globalState.getContext().Provider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Create the global state provider component.
 | 
					 | 
				
			||||||
	return useCallback((props: PropsWithChildren<{}>) => {
 | 
					 | 
				
			||||||
		// Get the current state of the global state.
 | 
					 | 
				
			||||||
		const [state, setState] = useState(globalState.getDefaultValue());
 | 
					 | 
				
			||||||
		// Pass the state update system to the global state.
 | 
					 | 
				
			||||||
		globalState.setStateUpdater(setState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Use the context provider and render children.
 | 
					 | 
				
			||||||
		return (
 | 
					 | 
				
			||||||
			<Provider value={state}>
 | 
					 | 
				
			||||||
				{props.children}
 | 
					 | 
				
			||||||
			</Provider>
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	}, [Provider]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Global state Provider component
 | 
					 | 
				
			||||||
 * @constructor
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function GlobalStateProvider<S, R extends GlobalStateReducers<S>>({globalState, children}: React.PropsWithChildren<{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Global state for which to get the global state Provider.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	globalState: GlobalState<S, R>;
 | 
					 | 
				
			||||||
}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the current global state provider.
 | 
					 | 
				
			||||||
	const Provider = useGlobalStateProvider(globalState);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		// Render the children inside the provider.
 | 
					 | 
				
			||||||
		<Provider>{children}</Provider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get the global state and its reducers.
 | 
					 | 
				
			||||||
 * @param globalState The global state to get.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useGlobalState<S, R extends GlobalStateReducers<S>>(globalState: GlobalState<S, R>): [S, R]
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the global state data (from its context state).
 | 
					 | 
				
			||||||
	const ctx = useContext(globalState.getContext());
 | 
					 | 
				
			||||||
	// Return the context state (global state data), and the global state reducers for this current state.
 | 
					 | 
				
			||||||
	return [ctx, globalState.getStateReducers(ctx)];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get the reducers of the global state.
 | 
					 | 
				
			||||||
 * @param globalState The global state for which to get the reducers.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useGlobalStateReducers<S, R extends GlobalStateReducers<S>>(globalState: GlobalState<S, R>): R
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Return the global state reducers for the current global state.
 | 
					 | 
				
			||||||
	return globalState.getStateReducers(useContext(globalState.getContext()));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get the global state data.
 | 
					 | 
				
			||||||
 * @param globalState The global state for which to get the data.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useGlobalStateValue<S, R extends GlobalStateReducers<S>>(globalState: GlobalState<S, R>): S
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Return the global state data (from its context state).
 | 
					 | 
				
			||||||
	return useContext(globalState.getContext());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,133 +0,0 @@
 | 
				
			||||||
import React, {useCallback, useContext, useMemo, useState} from "react";
 | 
					 | 
				
			||||||
import {v4 as uuidv4} from "uuid";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create Kernel context.
 | 
					 | 
				
			||||||
 * @param defaultValue Kernel context default value.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function createKernelContext<T>(defaultValue: T): KernelContext<T>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return {
 | 
					 | 
				
			||||||
		uuid: uuidv4(),
 | 
					 | 
				
			||||||
		defaultValue: defaultValue,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Kernel context definition object.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface KernelContext<T>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Kernel context UUID.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Kernel context default value.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	defaultValue: T;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Kernel context setter function type.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type KernelContextSetter<IdentifierType extends string|symbol|number = string, ValueType = any> = (identifier: IdentifierType, value: ValueType) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Kernel contexts type.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type KernelContexts = Record<string, any>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Kernel context dispatcher function type.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type KernelContextDispatcher<ValueType = any> = (value: ValueType) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Kernel global context data.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface KernelGlobalContextData
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	contexts: KernelContexts;
 | 
					 | 
				
			||||||
	setContext: KernelContextSetter;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * React Kernel global context.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const KernelGlobalContext = React.createContext<KernelGlobalContextData>({
 | 
					 | 
				
			||||||
	contexts: {},
 | 
					 | 
				
			||||||
	setContext: () => {},
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Kernel global context provider.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function KernelGlobalContextProvider({children}: React.PropsWithChildren<{}>)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Kernel contexts initialization.
 | 
					 | 
				
			||||||
	const [kernelContexts, setKernelContexts] = useState<KernelContexts>({});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Change kernel contexts.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const changeKernelContexts = useCallback((kernelContextsUpdate: Partial<KernelContexts>) => {
 | 
					 | 
				
			||||||
		setKernelContexts({
 | 
					 | 
				
			||||||
			...kernelContexts,
 | 
					 | 
				
			||||||
			...kernelContextsUpdate,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}, [kernelContexts, setKernelContexts]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Kernel context setter function.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const kernelContextSetter = useCallback((identifier: string, data: any) => (
 | 
					 | 
				
			||||||
		changeKernelContexts({
 | 
					 | 
				
			||||||
			[identifier]: data,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	), [changeKernelContexts]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize kernel global context value.
 | 
					 | 
				
			||||||
	const kernelContextValue = useMemo<KernelGlobalContextData>(() => ({
 | 
					 | 
				
			||||||
		contexts: kernelContexts,
 | 
					 | 
				
			||||||
		setContext: kernelContextSetter,
 | 
					 | 
				
			||||||
	}), [kernelContexts, kernelContextSetter]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return (
 | 
					 | 
				
			||||||
		<KernelGlobalContext.Provider value={kernelContextValue}>
 | 
					 | 
				
			||||||
			{children}
 | 
					 | 
				
			||||||
		</KernelGlobalContext.Provider>
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get kernel global context data.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function useKernelGlobalContext(): KernelGlobalContextData
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return useContext(KernelGlobalContext);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Initialize or get kernel context with given identifier.
 | 
					 | 
				
			||||||
 * @param context Context object.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useKernelContext<T>(context: KernelContext<T>): [T, KernelContextDispatcher<T>]
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Get kernel global context.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const kernelGlobalContext = useKernelGlobalContext();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Function to set kernel context.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	const setContext = useCallback(
 | 
					 | 
				
			||||||
		(newValue: T) => kernelGlobalContext.setContext(context.uuid, newValue),
 | 
					 | 
				
			||||||
		[kernelGlobalContext.setContext],
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Return kernel context and its dispatcher.
 | 
					 | 
				
			||||||
	return [kernelGlobalContext.contexts?.[context.uuid] ?? context.defaultValue, setContext];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,68 +0,0 @@
 | 
				
			||||||
import React, {DependencyList, FormEvent, useCallback, useEffect, useRef} from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type Modify<T, R> = Omit<T, keyof R> & R;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Merge multiple class names to one full class name.
 | 
					 | 
				
			||||||
 * @param className Class names.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function classes(...className: (string|null|undefined|false)[]): string
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return className.filter((className) => !!className).join(" ");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatDate(date: Date): string
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return ((date.getDate() < 10 ? "0" : "") + date.getDate()) + "/" + (((date.getMonth() + 1) < 10 ? "0" : "") + (date.getMonth() + 1)) + "/" + date.getFullYear();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function formatTime(date: Date): string
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return (date.getHours() < 10 ? "0" : "") + date.getHours() + ":" + (date.getMinutes() < 10 ? "0" : "") + date.getMinutes();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Normalize a given string for searching.
 | 
					 | 
				
			||||||
 * @param str The string to normalize.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function normalizeString(str: string): string
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return str
 | 
					 | 
				
			||||||
		? str.toLowerCase?.().normalize?.("NFD")
 | 
					 | 
				
			||||||
			.replace?.(/[\u0300-\u036f]/g, "")
 | 
					 | 
				
			||||||
		: "";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get the previous value of a given value.
 | 
					 | 
				
			||||||
 * @param currentValue The current value.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function usePreviousValue<T>(currentValue: T): T
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Get the reference to the previous value.
 | 
					 | 
				
			||||||
	const ref = useRef<T>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If the value has changed, saving it in the reference after rendering.
 | 
					 | 
				
			||||||
	useEffect(() => {
 | 
					 | 
				
			||||||
		ref.current = currentValue;
 | 
					 | 
				
			||||||
	}, [currentValue]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get the previous value.
 | 
					 | 
				
			||||||
	return ref?.current ?? undefined;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create a callback for a form submit function that fully overrides default form behavior.
 | 
					 | 
				
			||||||
 * @param submitFunction The function to call on form submit.
 | 
					 | 
				
			||||||
 * @param dependencies Submit function dependencies.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useFormSubmit(submitFunction: () => void, dependencies: DependencyList = []): React.FormEventHandler<HTMLFormElement>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// Use a memoized callback.
 | 
					 | 
				
			||||||
	return useCallback((event: React.FormEvent<HTMLFormElement>) => {
 | 
					 | 
				
			||||||
		// Prevent default behavior, then call submit function.
 | 
					 | 
				
			||||||
		event.preventDefault();
 | 
					 | 
				
			||||||
		submitFunction();
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
	}, [submitFunction, ...dependencies]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,91 +0,0 @@
 | 
				
			||||||
:root
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	@background-lightest: #FFFFFF; --background-lightest: @background-lightest;
 | 
					 | 
				
			||||||
	@background-lighter: #F7F7F7; --background-lighter: @background-lighter;
 | 
					 | 
				
			||||||
	@background: #EFEFEF; --background: @background;
 | 
					 | 
				
			||||||
	@background-darker: #E7E7E7; --background-darker: @background-darker;
 | 
					 | 
				
			||||||
	@background-darkest: #D9D9D9; --background-darkest: @background-darkest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@neutral: #909090; --neutral: @neutral;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@foreground-lightest: #1F1F1F; --foreground-lightest: @foreground-lightest;
 | 
					 | 
				
			||||||
	@foreground-lighter: #171717; --foreground-lighter: @foreground-lighter;
 | 
					 | 
				
			||||||
	@foreground: #0F0F0F; --foreground: @foreground;
 | 
					 | 
				
			||||||
	@foreground-darker: #080808; --foreground-darker: @foreground-darker;
 | 
					 | 
				
			||||||
	@foreground-darkest: #000000; --foreground-darkest: @foreground-darkest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@foreground-shadow: fade(@foreground, 33%); --foreground-shadow: @foreground-shadow;
 | 
					 | 
				
			||||||
	@curtain-dim: fade(@foreground, 50%); --curtain-dim: @curtain-dim;
 | 
					 | 
				
			||||||
	@curtain-inset: fade(@foreground, 20%); --curtain-inset: @curtain-inset;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@green-lighter: #22BD12; --green-lighter: @green-lighter;
 | 
					 | 
				
			||||||
	@green: #1DA90F; --green: @green;
 | 
					 | 
				
			||||||
	@green-darker: #159308; --green-darker: @green-darker;
 | 
					 | 
				
			||||||
	@green-background: #A9FFA0; --green-background: @green-background;
 | 
					 | 
				
			||||||
	@green-background-darker: #86F37E; --green-background-darker: @green-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@red-lighter: #E32424; --red-lighter: @red-lighter;
 | 
					 | 
				
			||||||
	@red: #D01212; --red: @red;
 | 
					 | 
				
			||||||
	@red-darker: #AF0707; --red-darker: @red-darker;
 | 
					 | 
				
			||||||
	@red-background: #FFBABA; --red-background: @red-background;
 | 
					 | 
				
			||||||
	@red-background-darker: #FFA6A6; --red-background-darker: @red-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@blue-lighter: #378AFF; --blue-lighter: @blue-lighter;
 | 
					 | 
				
			||||||
	@blue: #0D6DEE; --blue: @blue;
 | 
					 | 
				
			||||||
	@blue-darker: #0657C5; --blue-darker: @blue-darker;
 | 
					 | 
				
			||||||
	@blue-background: #9DC8FF; --blue-background: @blue-background;
 | 
					 | 
				
			||||||
	@blue-background-darker: #7EA9E1; --blue-background-darker: @blue-background-darker;
 | 
					 | 
				
			||||||
	@blue-gradient: linear-gradient(33deg, @blue 0%, @blue-lighter 100%), @blue; --blue-gradient: @blue-gradient;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@orange-lighter: #E77220; --orange-lighter: @orange-lighter;
 | 
					 | 
				
			||||||
	@orange: #D06112; --orange: @orange;
 | 
					 | 
				
			||||||
	@orange-darker: #BB5308; --orange-darker: @orange-darker;
 | 
					 | 
				
			||||||
	@orange-background: #FFC599; --orange-background: @orange-background;
 | 
					 | 
				
			||||||
	@orange-background-darker: #FFB47D; --orange-background-darker: @orange-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@pink-lighter: #EF56DF; --pink-lighter: @pink-lighter;
 | 
					 | 
				
			||||||
	@pink: #CE3EBF; --pink: @pink;
 | 
					 | 
				
			||||||
	@pink-darker: #B927AB; --pink-darker: @pink-darker;
 | 
					 | 
				
			||||||
	@pink-background: #FFABF4; --pink-background: @pink-background;
 | 
					 | 
				
			||||||
	@pink-background-darker: #EF8CE1; --pink-background-darker: @pink-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@purple-lighter: #9752FF; --purple-lighter: @purple-lighter;
 | 
					 | 
				
			||||||
	@purple: #7D2AFF; --purple: @purple;
 | 
					 | 
				
			||||||
	@purple-darker: #6610EE; --purple-darker: @purple-darker;
 | 
					 | 
				
			||||||
	@purple-background: #C8A5FF; --purple-background: @purple-background;
 | 
					 | 
				
			||||||
	@purple-background-darker: #B489F6; --purple-background-darker: @purple-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@yellow-lighter: #F8DF3D; --yellow-lighter: @yellow-lighter;
 | 
					 | 
				
			||||||
	@yellow: #EACD0D; --yellow: @yellow;
 | 
					 | 
				
			||||||
	@yellow-darker: #D3B803; --yellow-darker: @yellow-darker;
 | 
					 | 
				
			||||||
	@yellow-background: #FFF195; --yellow-background: @yellow-background;
 | 
					 | 
				
			||||||
	@yellow-background-darker: #ECDB71; --yellow-background-darker: @yellow-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@brown-lighter: #5E2617; --brown-lighter: @brown-lighter;
 | 
					 | 
				
			||||||
	@brown: #4B190C; --brown: @brown;
 | 
					 | 
				
			||||||
	@brown-darker: #3B1105; --brown-darker: @brown-darker;
 | 
					 | 
				
			||||||
	@brown-background: #B6968F; --brown-background: @brown-background;
 | 
					 | 
				
			||||||
	@brown-background-darker: #A97E74; --brown-background-darker: @brown-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@primary-lighter: @blue-lighter; --primary-lighter: @primary-lighter;
 | 
					 | 
				
			||||||
	@primary: @blue; --primary: @primary;
 | 
					 | 
				
			||||||
	@primary-darker: @blue-darker; --primary-darker: @primary-darker;
 | 
					 | 
				
			||||||
	@primary-background: @blue-background; --primary-background: @primary-background;
 | 
					 | 
				
			||||||
	@primary-background-darker: @blue-background-darker; --primary-background-darker: @primary-background-darker;
 | 
					 | 
				
			||||||
	@primary-gradient: @blue-gradient; --primary-gradient: @primary-gradient;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@secondary-lighter: @orange-lighter; --secondary-lighter: @secondary-lighter;
 | 
					 | 
				
			||||||
	@secondary: @orange; --secondary: @secondary;
 | 
					 | 
				
			||||||
	@secondary-darker: @orange-darker; --secondary-darker: @secondary-darker;
 | 
					 | 
				
			||||||
	@secondary-background: @orange-background; --secondary-background: @secondary-background;
 | 
					 | 
				
			||||||
	@secondary-background-darker: @orange-background-darker; --secondary-background-darker: @secondary-background-darker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@menu-hover: rgba(255, 255, 255, 0.15); --menu-hover: @menu-hover;
 | 
					 | 
				
			||||||
	@menu-active: rgba(0, 0, 0, 0.125); --menu-active: @menu-active;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,38 +0,0 @@
 | 
				
			||||||
html, body
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 0;
 | 
					 | 
				
			||||||
	padding: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
html, body, input, button, textarea
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 1em;
 | 
					 | 
				
			||||||
	font-family: var(--sans-serif-fonts);
 | 
					 | 
				
			||||||
	font-weight: 500;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pre, code
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-family: var(--monospace-fonts);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	background: var(--background);
 | 
					 | 
				
			||||||
	color: var(--foreground);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
p
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 0.75em auto;
 | 
					 | 
				
			||||||
	line-height: 1.6em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.virtual
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	position: absolute;
 | 
					 | 
				
			||||||
	display: block;
 | 
					 | 
				
			||||||
	height: 0;
 | 
					 | 
				
			||||||
	opacity: 0;
 | 
					 | 
				
			||||||
	pointer-events: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,23 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
@import "components/_box";
 | 
					 | 
				
			||||||
@import "components/_button";
 | 
					 | 
				
			||||||
@import "components/_card";
 | 
					 | 
				
			||||||
@import "components/_curtains";
 | 
					 | 
				
			||||||
@import "components/_dates";
 | 
					 | 
				
			||||||
@import "components/_errors";
 | 
					 | 
				
			||||||
@import "components/_floating";
 | 
					 | 
				
			||||||
@import "components/_form";
 | 
					 | 
				
			||||||
@import "components/_headings";
 | 
					 | 
				
			||||||
@import "components/_link";
 | 
					 | 
				
			||||||
@import "components/_list";
 | 
					 | 
				
			||||||
@import "components/_loaders";
 | 
					 | 
				
			||||||
@import "components/_menus";
 | 
					 | 
				
			||||||
@import "components/_modal";
 | 
					 | 
				
			||||||
@import "components/_notifications";
 | 
					 | 
				
			||||||
@import "components/_pagination";
 | 
					 | 
				
			||||||
@import "components/_select";
 | 
					 | 
				
			||||||
@import "components/_steps";
 | 
					 | 
				
			||||||
@import "components/_steps-counter";
 | 
					 | 
				
			||||||
@import "components/_subapp";
 | 
					 | 
				
			||||||
@import "components/_table";
 | 
					 | 
				
			||||||
@import "components/_tip";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
@import "@fontsource-variable/manrope";
 | 
					 | 
				
			||||||
@import "@fontsource-variable/source-serif-4";
 | 
					 | 
				
			||||||
@import "@fontsource-variable/jetbrains-mono";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
:root
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	@sans-serif-fonts: "Manrope Variable", sans-serif;
 | 
					 | 
				
			||||||
	@serif-fonts: "Source Serif 4 Variable", serif;
 | 
					 | 
				
			||||||
	@monospace-fonts: "Jetbrains Mono Variable", monospace;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	--sans-serif-fonts: @sans-serif-fonts;
 | 
					 | 
				
			||||||
	--serif-fonts: @serif-fonts;
 | 
					 | 
				
			||||||
	--monospace-fonts: @monospace-fonts;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
footer
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 4em auto;
 | 
					 | 
				
			||||||
	color: var(--foreground-lightest);
 | 
					 | 
				
			||||||
	text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
div.box
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 0.5em auto;
 | 
					 | 
				
			||||||
	width: 50em;
 | 
					 | 
				
			||||||
	max-width: 95%;
 | 
					 | 
				
			||||||
	box-sizing: border-box;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h1, h2, h3, h4, h5, h6
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 0.33em 0 0.5em 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,198 +0,0 @@
 | 
				
			||||||
a.button, button, input[type="submit"], input[type="reset"]
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	transition: background 0.2s ease, outline 0.2s ease, transform 0.1s ease;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	display: inline-block;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	margin: 0.2em 0.33em;
 | 
					 | 
				
			||||||
	padding: 0.45em 0.66em;
 | 
					 | 
				
			||||||
	width: fit-content;
 | 
					 | 
				
			||||||
	border-radius: 0.25em;
 | 
					 | 
				
			||||||
	box-sizing: border-box;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	box-shadow: 0 0 0.3em 0 var(--foreground-shadow);
 | 
					 | 
				
			||||||
	outline: solid 2px transparent;
 | 
					 | 
				
			||||||
	outline-offset: 2px;
 | 
					 | 
				
			||||||
	border: solid var(--primary-darker) thin;
 | 
					 | 
				
			||||||
	background: var(--primary);
 | 
					 | 
				
			||||||
	color: var(--background);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	font-weight: 600;
 | 
					 | 
				
			||||||
	text-decoration: none;
 | 
					 | 
				
			||||||
	vertical-align: middle;
 | 
					 | 
				
			||||||
	text-align: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	transform: scale(1);
 | 
					 | 
				
			||||||
	transform-origin: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&:hover
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		background: var(--primary-lighter);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	&:focus
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		outline: solid 2px var(--primary);
 | 
					 | 
				
			||||||
		outline-offset: 2px;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&:active
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transform: scale(0.95);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&:disabled
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		opacity: 60%;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.flat
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		box-shadow: 0 0 0 0 transparent;
 | 
					 | 
				
			||||||
		border-color: var(--background-darkest);
 | 
					 | 
				
			||||||
		background: var(--background-lighter);
 | 
					 | 
				
			||||||
		color: var(--foreground);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			background: var(--background-lightest);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		&:focus
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			outline-color: var(--neutral);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.green, &.validation, &.ok, &.save, &.positive, &.good, &.success
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		border-color: var(--green-darker);
 | 
					 | 
				
			||||||
		background: var(--green);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			background: var(--green-lighter);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		&:focus
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			outline-color: var(--green);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.flat
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border-color: var(--green-background);
 | 
					 | 
				
			||||||
			background: var(--green-background);
 | 
					 | 
				
			||||||
			color: var(--green);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			&:hover
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				background: var(--green-background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			&:focus
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				outline-color: var(--green-background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.orange, &.cancel, &.back, &.return, &.warning
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		border-color: var(--orange-darker);
 | 
					 | 
				
			||||||
		background: var(--orange);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			background: var(--orange-lighter);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		&:focus
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			outline-color: var(--orange);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.flat
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border-color: var(--orange-background);
 | 
					 | 
				
			||||||
			background: var(--orange-background);
 | 
					 | 
				
			||||||
			color: var(--orange);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			&:hover
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				background: var(--orange-background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			&:focus
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				outline-color: var(--orange-background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.red, &.delete, &.remove, &.close, &.no, &.negative, &.bad, &.error
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		border-color: var(--red-darker);
 | 
					 | 
				
			||||||
		background: var(--red);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			background: var(--red-lighter);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		&:focus
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			outline-color: var(--red);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.flat
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border-color: var(--red-background);
 | 
					 | 
				
			||||||
			background: var(--red-background);
 | 
					 | 
				
			||||||
			color: var(--red);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			&:hover
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				background: var(--red-background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			&:focus
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				outline-color: var(--red-background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	svg
 | 
					 | 
				
			||||||
	{ // Icon style.
 | 
					 | 
				
			||||||
		display: inline-block;
 | 
					 | 
				
			||||||
		margin-top: -0.2em;
 | 
					 | 
				
			||||||
		margin-right: 0.2em;
 | 
					 | 
				
			||||||
		vertical-align: middle;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	&.icon-only svg
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin-right: 0.05em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.buttons
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	flex-direction: row;
 | 
					 | 
				
			||||||
	gap: 0.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	margin: 0.25em auto;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.right
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		justify-content: flex-end;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	&.left
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		justify-content: flex-start;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	&.center
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		justify-content: center;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	button, .button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,30 +0,0 @@
 | 
				
			||||||
.card
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 0.5em auto;
 | 
					 | 
				
			||||||
	padding: 1.4em;
 | 
					 | 
				
			||||||
	width: 50em;
 | 
					 | 
				
			||||||
	max-width: 92%;
 | 
					 | 
				
			||||||
	box-sizing: border-box;
 | 
					 | 
				
			||||||
	border-radius: 0.25em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	border: solid var(--background-darkest) thin;
 | 
					 | 
				
			||||||
	background: var(--background-lighter);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h1, h2, h3, h4, h5, h6
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 0.33em 0 0.5em 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> p
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		&:first-child
 | 
					 | 
				
			||||||
		{ margin-top: auto; }
 | 
					 | 
				
			||||||
		&:last-child
 | 
					 | 
				
			||||||
		{ margin-bottom: auto; }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.floating
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		width: 20em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
body > .curtain
 | 
					 | 
				
			||||||
{ // Position the curtain on top of everything and dim the background.
 | 
					 | 
				
			||||||
	position: fixed;
 | 
					 | 
				
			||||||
	top: 0;
 | 
					 | 
				
			||||||
	left: 0;
 | 
					 | 
				
			||||||
	width: 100vw;
 | 
					 | 
				
			||||||
	height: 100vh;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	box-shadow: inset 0 0 5em 0 var(--curtain-inset);
 | 
					 | 
				
			||||||
	background: var(--curtain-dim);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Show an animation when entering screen.
 | 
					 | 
				
			||||||
	animation: curtain-in 0.4s ease-in;
 | 
					 | 
				
			||||||
	transform-origin: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	z-index: 1000; // On top of main content.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.closed
 | 
					 | 
				
			||||||
	{ // Added when the curtain is closing and will soon be removed from DOM.
 | 
					 | 
				
			||||||
		transition: transform 0.4s ease-out, filter 0.4s ease-out, opacity 0.4s ease-out;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		transform: scale(1.2);
 | 
					 | 
				
			||||||
		filter: blur(0.5em);
 | 
					 | 
				
			||||||
		opacity: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pointer-events: none;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body.dimmed
 | 
					 | 
				
			||||||
{ // Disable scroll and blur the content when the body is dimmed.
 | 
					 | 
				
			||||||
	overflow: hidden;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> *:not(.curtain):not(.notifications)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transition: filter 0.4s ease-in;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		filter: blur(0.25em);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@keyframes curtain-in
 | 
					 | 
				
			||||||
{ // Screen enter animation.
 | 
					 | 
				
			||||||
	from
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transform: scale(1.2);
 | 
					 | 
				
			||||||
		filter: blur(0.5em);
 | 
					 | 
				
			||||||
		opacity: 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	to
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transform: scale(1);
 | 
					 | 
				
			||||||
		filter: blur(0);
 | 
					 | 
				
			||||||
		opacity: 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
@import "dates/_calendar";
 | 
					 | 
				
			||||||
@import "dates/_datepicker";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,60 +0,0 @@
 | 
				
			||||||
main.error
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 1.2em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> svg
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		display: block;
 | 
					 | 
				
			||||||
		margin: 1em auto;
 | 
					 | 
				
			||||||
		color: var(--red);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	hr
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 1em auto;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		width: 20em;
 | 
					 | 
				
			||||||
		max-width: 50%;
 | 
					 | 
				
			||||||
		height: 0.25em;
 | 
					 | 
				
			||||||
		border-radius: 0.1em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		border: none;
 | 
					 | 
				
			||||||
		background: var(--red);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h1, h2, h3, h4, h5, h6
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: auto;
 | 
					 | 
				
			||||||
		text-align: center;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	p, pre
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 1em auto;
 | 
					 | 
				
			||||||
		width: 40em;
 | 
					 | 
				
			||||||
		max-width: 95%;
 | 
					 | 
				
			||||||
		text-align: center;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		display: block;
 | 
					 | 
				
			||||||
		margin: 1em auto;
 | 
					 | 
				
			||||||
		padding: 0.66em 1em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.details
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		font-size: 0.8em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		button
 | 
					 | 
				
			||||||
		{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pre
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			width: auto;
 | 
					 | 
				
			||||||
			text-align: left;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,2 +0,0 @@
 | 
				
			||||||
@import "floating/_floating";
 | 
					 | 
				
			||||||
@import "floating/_tooltip";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
@import "forms/_box";
 | 
					 | 
				
			||||||
@import "forms/_datepicker-input";
 | 
					 | 
				
			||||||
@import "forms/_input";
 | 
					 | 
				
			||||||
@import "forms/_label";
 | 
					 | 
				
			||||||
@import "forms/_password-input";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,49 +0,0 @@
 | 
				
			||||||
h1, h2, h3, h4, h5, h6
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 0.8em auto;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.center, &.main
 | 
					 | 
				
			||||||
	{ text-align: center; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> svg
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		position: relative;
 | 
					 | 
				
			||||||
		bottom: -0.15em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h1
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 2.5em;
 | 
					 | 
				
			||||||
	font-weight: 800;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h2
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 2em;
 | 
					 | 
				
			||||||
	font-weight: 800;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h3
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 1.7em;
 | 
					 | 
				
			||||||
	font-weight: 700;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h4
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 1.5em;
 | 
					 | 
				
			||||||
	font-weight: 600;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h5
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 1.3em;
 | 
					 | 
				
			||||||
	font-weight: 600;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h6
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	font-size: 1.15em;
 | 
					 | 
				
			||||||
	font-weight: 600;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
a
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	color: var(--primary);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
ul
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	list-style: "–";
 | 
					 | 
				
			||||||
	padding: 0 2em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> li
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		padding: 0 0 0 0.5em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ul, ol
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: 1em auto;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
@import "loaders/_animated-background";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@import "loaders/_generic";
 | 
					 | 
				
			||||||
@import "loaders/_list";
 | 
					 | 
				
			||||||
@import "loaders/_spinning";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
@import "menus/_apps-menu";
 | 
					 | 
				
			||||||
@import "menus/_main-menu";
 | 
					 | 
				
			||||||
@import "menus/_submenu";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,99 +0,0 @@
 | 
				
			||||||
.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); }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,86 +0,0 @@
 | 
				
			||||||
body > ul.notifications
 | 
					 | 
				
			||||||
{ // Notifications list.
 | 
					 | 
				
			||||||
	position: fixed;
 | 
					 | 
				
			||||||
	top: 0;
 | 
					 | 
				
			||||||
	right: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	margin: 0 1em 1em;
 | 
					 | 
				
			||||||
	padding: 0;
 | 
					 | 
				
			||||||
	width: 20em;
 | 
					 | 
				
			||||||
	max-width: 66%;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	list-style: none;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	z-index: 2000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> li.notification
 | 
					 | 
				
			||||||
	{ // A single notification.
 | 
					 | 
				
			||||||
		margin: 1em auto;
 | 
					 | 
				
			||||||
		padding: 1em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		border-radius: 0.25em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		box-shadow: 0 0 0.5em 0 var(--foreground-shadow);
 | 
					 | 
				
			||||||
		background: var(--background);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Show an animation when entering screen.
 | 
					 | 
				
			||||||
		animation: notification-in 0.3s ease-in;
 | 
					 | 
				
			||||||
		transform-origin: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.closed
 | 
					 | 
				
			||||||
		{ // Added when the notification is closing and will soon be removed from DOM.
 | 
					 | 
				
			||||||
			transition: transform 0.3s ease-out, filter 0.3s ease-out, opacity 0.3s ease-out;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			transform: scale(1.15);
 | 
					 | 
				
			||||||
			filter: blur(0.25em);
 | 
					 | 
				
			||||||
			opacity: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			pointer-events: none;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.info
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border: solid var(--primary-darker) thin;
 | 
					 | 
				
			||||||
			background: var(--primary);
 | 
					 | 
				
			||||||
			color: var(--background);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.success
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border: solid var(--green-darker) thin;
 | 
					 | 
				
			||||||
			background: var(--green);
 | 
					 | 
				
			||||||
			color: var(--background);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.warning
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border: solid var(--orange-darker) thin;
 | 
					 | 
				
			||||||
			background: var(--orange);
 | 
					 | 
				
			||||||
			color: var(--background);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.error
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			border: solid var(--red-darker) thin;
 | 
					 | 
				
			||||||
			background: var(--red);
 | 
					 | 
				
			||||||
			color: var(--background);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@keyframes notification-in
 | 
					 | 
				
			||||||
{ // Screen enter animation.
 | 
					 | 
				
			||||||
	from
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transform: scale(1.15);
 | 
					 | 
				
			||||||
		filter: blur(0.25em);
 | 
					 | 
				
			||||||
		opacity: 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	to
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transform: scale(1);
 | 
					 | 
				
			||||||
		filter: blur(0);
 | 
					 | 
				
			||||||
		opacity: 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
@import "pagination/_nav";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
@import "select/_input";
 | 
					 | 
				
			||||||
@import "select/_selected";
 | 
					 | 
				
			||||||
@import "select/_suggestions";
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
.steps-counter
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	counter-reset: steps-count 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.step
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		&::before
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			content: counter(steps-count);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			display: inline-block;
 | 
					 | 
				
			||||||
			margin: 0 0.2em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			color: var(--primary);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			font-family: var(--monospace-fonts);
 | 
					 | 
				
			||||||
			font-size: 1.5em;
 | 
					 | 
				
			||||||
			font-weight: 700;
 | 
					 | 
				
			||||||
			vertical-align: baseline;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		counter-increment: steps-count;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,90 +0,0 @@
 | 
				
			||||||
div.steps
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	flex-direction: row;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> nav.steps
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		position: sticky;
 | 
					 | 
				
			||||||
		top: 1em;
 | 
					 | 
				
			||||||
		bottom: 1em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		flex-direction: column;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		justify-content: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		margin: auto 0.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> ul
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			margin: 0;
 | 
					 | 
				
			||||||
			padding: 0;
 | 
					 | 
				
			||||||
			list-style: none;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			> li
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				margin: auto;
 | 
					 | 
				
			||||||
				padding: 0;
 | 
					 | 
				
			||||||
				text-align: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				> button
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					padding: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					box-shadow: 0 0 0 0 transparent;
 | 
					 | 
				
			||||||
					outline: none;
 | 
					 | 
				
			||||||
					border: none;
 | 
					 | 
				
			||||||
					background: none;
 | 
					 | 
				
			||||||
					color: var(--foreground-lightest);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					&:focus::before
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						outline-color: var(--primary);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					&::before
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						transition: all 0.2s ease;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						content: "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						display: block;
 | 
					 | 
				
			||||||
						margin: auto auto 0.4em auto;
 | 
					 | 
				
			||||||
						width: 2em;
 | 
					 | 
				
			||||||
						height: 2em;
 | 
					 | 
				
			||||||
						border-radius: 2em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						box-shadow: 0 0 0 0 transparent;
 | 
					 | 
				
			||||||
						outline: solid 2px transparent;
 | 
					 | 
				
			||||||
						outline-offset: 2px;
 | 
					 | 
				
			||||||
						border: solid var(--background-darkest) thin;
 | 
					 | 
				
			||||||
						background: var(--background-lightest);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						transform: scale(0.75);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				&.active
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					> button
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						&::before
 | 
					 | 
				
			||||||
						{
 | 
					 | 
				
			||||||
							box-shadow: 0 0 0.3em 0 var(--foreground-shadow);
 | 
					 | 
				
			||||||
							border-color: var(--primary-darker);
 | 
					 | 
				
			||||||
							background: var(--primary);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							transform: scale(1);
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> :not(nav)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		flex: 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,76 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
@subapp-margin: 1.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.curtain > .subapp
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	position: absolute;
 | 
					 | 
				
			||||||
	top: @subapp-margin;
 | 
					 | 
				
			||||||
	left: @subapp-margin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	flex-direction: column;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	width: calc(100% - (2 * @subapp-margin));
 | 
					 | 
				
			||||||
	height: calc(100% - (2 * @subapp-margin));
 | 
					 | 
				
			||||||
	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.66em;
 | 
					 | 
				
			||||||
			font-weight: 650;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> .close
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			transition: opacity 0.2s ease;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			width: 3em;
 | 
					 | 
				
			||||||
			height: 3em;
 | 
					 | 
				
			||||||
			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
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		flex: 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		padding: 1em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		overflow: auto;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,44 +0,0 @@
 | 
				
			||||||
table
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	margin: auto;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	border-collapse: collapse;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	background: var(--background-lighter);
 | 
					 | 
				
			||||||
	//border: solid var(--background-darkest) thin;
 | 
					 | 
				
			||||||
	border-radius: 0.25em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	thead
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tbody
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		tr
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			&:nth-child(even)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				background: var(--background-lightest);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			&:hover
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				background: var(--background-darker);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			&:last-child { border-bottom: none; }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tr
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transition: background 0.1s ease;
 | 
					 | 
				
			||||||
		border-bottom: solid var(--background-darkest) thin;
 | 
					 | 
				
			||||||
		background: transparent;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	th, td
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		padding: 0.4em 0.6em;
 | 
					 | 
				
			||||||
		text-align: left;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,28 +0,0 @@
 | 
				
			||||||
p.tip
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	position: relative;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	margin: auto;
 | 
					 | 
				
			||||||
	width: 30em;
 | 
					 | 
				
			||||||
	padding: 0.3em 0.5em 0.3em 3em;
 | 
					 | 
				
			||||||
	border-radius: 0.3em;
 | 
					 | 
				
			||||||
	box-sizing: border-box;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	border: solid var(--background-darkest) thin;
 | 
					 | 
				
			||||||
	color: var(--foreground-lightest);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	svg
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		position: absolute;
 | 
					 | 
				
			||||||
		top: 0.3em;
 | 
					 | 
				
			||||||
		left: 0.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		display: inline-block;
 | 
					 | 
				
			||||||
		margin: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		color: var(--primary);
 | 
					 | 
				
			||||||
		font-size: 1.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		vertical-align: middle;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
table.calendar
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	thead th
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		text-align: center;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tbody > tr
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		border-bottom: none;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:nth-child(even) { background: transparent; }
 | 
					 | 
				
			||||||
		&:hover { background: transparent; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> td
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			padding: 0;
 | 
					 | 
				
			||||||
			text-align: center;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	a.day
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		transition: background 0.15s ease;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		display: inline-flex;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		justify-content: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		width: 2.2em;
 | 
					 | 
				
			||||||
		height: 2.2em;
 | 
					 | 
				
			||||||
		border-radius: 50%;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		background: transparent;
 | 
					 | 
				
			||||||
		color: var(--foreground);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		font-weight: 400;
 | 
					 | 
				
			||||||
		text-align: center;
 | 
					 | 
				
			||||||
		cursor: pointer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			background: var(--background-darker);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.selected
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			background: var(--primary);
 | 
					 | 
				
			||||||
			color: var(--background);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.faded
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			opacity: 0.4;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,44 +0,0 @@
 | 
				
			||||||
.datepicker
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	position: relative;
 | 
					 | 
				
			||||||
	padding: 0 1em;
 | 
					 | 
				
			||||||
	background: var(--background-lighter);
 | 
					 | 
				
			||||||
	font-size: 0.9em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.year-month
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		font-size: 1.1em;
 | 
					 | 
				
			||||||
		text-align: center;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		&.previous-month, &.next-month
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			position: absolute;
 | 
					 | 
				
			||||||
			top: 0;
 | 
					 | 
				
			||||||
			bottom: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			display: flex;
 | 
					 | 
				
			||||||
			align-items: center;
 | 
					 | 
				
			||||||
			justify-content: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			margin: auto;
 | 
					 | 
				
			||||||
			padding: 0;
 | 
					 | 
				
			||||||
			width: 2em;
 | 
					 | 
				
			||||||
			height: 2em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			svg
 | 
					 | 
				
			||||||
			{ margin: 0; }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&.previous-month
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			left: -1.1em;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		&.next-month
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			right: -1.1em;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
:not(.card).floating
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	align-self: center;
 | 
					 | 
				
			||||||
	justify-content: center;
 | 
					 | 
				
			||||||
	z-index: 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .card.floating
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 0.4em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,13 +0,0 @@
 | 
				
			||||||
.card.floating.tooltip
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	width: unset;
 | 
					 | 
				
			||||||
	max-width: 20em;
 | 
					 | 
				
			||||||
	padding: 0.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	border-color: var(--foreground-darker);
 | 
					 | 
				
			||||||
	background: var(--foreground-lightest);
 | 
					 | 
				
			||||||
	color: var(--background);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	font-size: 0.9em;
 | 
					 | 
				
			||||||
	text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,84 +0,0 @@
 | 
				
			||||||
label.box, label.toggleswitch
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	display: block;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&::before
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		content: unset;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> input
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		position: absolute;
 | 
					 | 
				
			||||||
		pointer-events: none;
 | 
					 | 
				
			||||||
		opacity: 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> input[type="radio"] + .button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		border-radius: 50%;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin: 0 0.6em 0.12em 0;
 | 
					 | 
				
			||||||
		width: 1.5em;
 | 
					 | 
				
			||||||
		height: 1.5em;
 | 
					 | 
				
			||||||
		padding: 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		border: none;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		color: var(--background-darkest);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> svg
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			display: block;
 | 
					 | 
				
			||||||
			margin: 0.22em auto 0;
 | 
					 | 
				
			||||||
			vertical-align: middle;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		vertical-align: middle;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&:focus-within > .button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		outline-color: var(--primary);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> input:not(:checked) + .button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		background: var(--background-lighter);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
label.toggleswitch
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	> .button
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		display: inline-flex;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		width: 2.5em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> .switch
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			transition: margin-left 0.25s ease, box-shadow 0.25s ease;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			display: inline-block;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			margin: auto 0.25em;
 | 
					 | 
				
			||||||
			width: 0.9em;
 | 
					 | 
				
			||||||
			height: 0.8em;
 | 
					 | 
				
			||||||
			border-radius: 0.25em;
 | 
					 | 
				
			||||||
			box-shadow: 0 0 0 0 var(--foreground-shadow);
 | 
					 | 
				
			||||||
			border: solid var(--background-darkest) thin;
 | 
					 | 
				
			||||||
			background: var(--background);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> input:checked + .button > .switch
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		margin-left: 1.2em;
 | 
					 | 
				
			||||||
		border-color: transparent;
 | 
					 | 
				
			||||||
		box-shadow: 0 0 0.4em 0 var(--foreground-shadow);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
label.datepicker-input
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	.card.datepicker
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		padding: 0.2em;
 | 
					 | 
				
			||||||
		width: fit-content;
 | 
					 | 
				
			||||||
		max-width: unset;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		
		Reference in a new issue