Add loaders.

This commit is contained in:
Madeorsk 2024-07-06 16:52:26 +02:00
parent e15861393e
commit bc79307ff7
Signed by: Madeorsk
SSH key fingerprint: SHA256:J9G0ofIOLKf7kyS2IfrMqtMaPdfsk1W02+oGueZzDDU
10 changed files with 212 additions and 9 deletions

View file

@ -11,6 +11,9 @@ 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";
export function DemoApp()
{
@ -25,7 +28,6 @@ export function DemoApp()
<h2>TODO</h2>
<ul>
<li>Loaders</li>
<li>Dropdown menus</li>
<li>Main menu</li>
<li>Tabs / Apps selectors</li>
@ -57,20 +59,20 @@ export function DemoApp()
<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 weight={"bold"} /> A validation button</button>
<button type={"button"} className={"cancel"}><XCircle weight={"bold"} /> A cancellation button</button>
<button type={"button"} className={"delete"}><TrashSimple weight={"bold"} /> A deletion button</button>
<button type={"button"} className={"validation"}><FloppyDisk weight={"bold"}/> A validation button</button>
<button type={"button"} className={"cancel"}><XCircle weight={"bold"}/> A cancellation button</button>
<button type={"button"} className={"delete"}><TrashSimple weight={"bold"}/> A deletion button</button>
<h2>Forms</h2>
<form>
<label>
Text label <RequiredField />
<input type={"text"} placeholder={"Normal demo text"} required={true} />
Text label <RequiredField/>
<input type={"text"} placeholder={"Normal demo text"} required={true}/>
</label>
<label>
Textarea label <RequiredField />
Textarea label <RequiredField/>
<textarea placeholder={"A normal textarea."} required={true}></textarea>
</label>
@ -110,7 +112,8 @@ export function DemoApp()
<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
<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
@ -226,7 +229,7 @@ export function DemoApp()
<button>Click me!</button>
</Float>
<Float mode={"always"} content={"I am always shown."} floatingOptions={{ placement: "top" }}>
<Float mode={"always"} content={"I am always shown."} floatingOptions={{placement: "top"}}>
<button>Why always me?</button>
</Float>
@ -245,6 +248,21 @@ export function DemoApp()
</Card>
<h2>Loaders</h2>
<h3>Simple loaders</h3>
<Card>
<SpinningLoader/>
<ListLoader/>
</Card>
<h3>Generic loader</h3>
<GenericLoader>
<Card>
Sample content.
</Card>
</GenericLoader>
</main>
);
}

View file

@ -0,0 +1,10 @@
import React from "react";
export function GenericLoader({children}: React.PropsWithChildren<{}>)
{
return (
<div className={"generic loader"}>
{children}
</div>
);
}

View file

@ -0,0 +1,37 @@
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>
);
}

View file

@ -0,0 +1,8 @@
import React from "react";
export function SpinningLoader()
{
return (
<div className={"spinning loader"}></div>
);
}

View file

@ -7,6 +7,7 @@
@import "components/_headings";
@import "components/_link";
@import "components/_list";
@import "components/_loaders";
@import "components/_select";
@import "components/_steps";
@import "components/_table";

View file

@ -0,0 +1,5 @@
@import "loaders/_animated-background";
@import "loaders/_generic";
@import "loaders/_list";
@import "loaders/_spinning";

View file

@ -0,0 +1,21 @@
@loaders-animated-background-color: linear-gradient(111deg, var(--background-darkest) 0%, var(--background-lightest) 50%, var(--background-darkest) 100%), var(--background-darkest);
@keyframes loaders-animated-background
{
0%
{
background: @loaders-animated-background-color 10% 0;
background-size: 300% 300%;
}
50%
{
background-position: 91% 100%;
}
100%
{
background-size: 300% 300%;
background-position: 10% 0;
}
}

View file

@ -0,0 +1,13 @@
.generic.loader
{
margin: auto;
width: fit-content;
border-radius: 0.25em;
border: solid var(--background) thin;
background: @loaders-animated-background-color;
color: transparent;
* { background: none; color: transparent; border: none; box-shadow: 0 0 0 0 transparent; outline: none; }
animation: loaders-animated-background 4s linear infinite alternate;
}

View file

@ -0,0 +1,49 @@
ul.list.loader
{
margin: 1em auto;
padding: 0;
list-style: none;
> li
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: stretch;
margin: 1em auto;
&::before
{
content: "";
display: inline-block;
align-self: center;
margin: 0 0.66em 0 0;
width: 2em;
height: 2em;
border-radius: 2em;
border: solid var(--background) thin;
background: @loaders-animated-background-color;
animation: loaders-animated-background 4s linear infinite alternate;
vertical-align: middle;
}
.content
{
flex: 1;
position: relative;
padding: 0.5em 1em;
border-radius: 2em;
border: solid var(--background) thin;
background: @loaders-animated-background-color;
color: transparent;
animation: loaders-animated-background 4s linear infinite alternate;
}
}
}

View file

@ -0,0 +1,41 @@
.spinning.loader
{
position: relative;
display: inline-block;
margin: 1em;
width: 2.5em;
height: 2.5em;
border-radius: 50%;
box-sizing: border-box;
border: 0.3em solid var(--foreground);
animation: spinning-loader-rotation 1s linear infinite;
&::after
{
content: "";
box-sizing: border-box;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 3.5em;
height: 3.5em;
border-radius: 50%;
border: 0.3em solid transparent;
border-bottom-color: var(--primary);
}
}
@keyframes spinning-loader-rotation
{
0%
{
transform: rotate(0deg);
}
100%
{
transform: rotate(360deg);
}
}