mirror of
https://github.com/picocss/pico.git
synced 2025-04-20 16:46:14 -04:00
V. 2.1.0 - Yohns Fork
This commit is contained in:
parent
b611b528bc
commit
42b62b10a6
10 changed files with 66 additions and 38 deletions
407
docs/index.html
Normal file
407
docs/index.html
Normal file
|
@ -0,0 +1,407 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Preview • Pico CSS</title>
|
||||
<meta name="description" content="A pure HTML example, without dependencies." />
|
||||
<!-- Pico.css -->
|
||||
<link rel="stylesheet" href="./../css/pico.lime.min.css" />
|
||||
<link rel="stylesheet" href="./../css/pico.colors.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="col-12 col-md-3 col-lg-2">
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<hgroup>
|
||||
<h1>Pico</h1>
|
||||
<p>A Pico CSS + example with addition to the standard Pico CSS library.</p>
|
||||
<p>Demo page from <a href="https://picocss.com/docs/modal#utilities">picocss.com model docs</a></p>
|
||||
</hgroup>
|
||||
<details class="dropdown">
|
||||
<summary role="button" class="secondary">Theme</summary>
|
||||
<ul>
|
||||
<li><a href="#" data-theme-switcher="auto">Auto</a></li>
|
||||
<li><a href="#" data-theme-switcher="light">Light</a></li>
|
||||
<li><a href="#" data-theme-switcher="dark">Dark</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<aside>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#validation">Validation</a></li>
|
||||
<li><a href="#group">Group</a></li>
|
||||
<li><a href="#rows">Row</a></li>
|
||||
<li><a href="#row-offsets">Row Offset</a></li>
|
||||
<li><a href="#row-alignments">Row Alignments</a></li>
|
||||
<li><a href="#row-breakpoints">Row Breakpoints</a></li>
|
||||
<li><a href="#modal">Modals</a></li>
|
||||
<li><a href="#notifications">Notifications</a></li>
|
||||
<li><a href="#accordions">Accorddions</a></li>
|
||||
<li><a href="#timeline">Timeline</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</header>
|
||||
<!-- /Header -->
|
||||
</div>
|
||||
<main class="col-12 col-md-9 col-lg-10">
|
||||
<article>
|
||||
<header><h2>Yohns PicoCSS Fork</h2></header>
|
||||
<p>I've merged some open pull requests from the <a href="https://github.com/picocss/pico">original Pico</a> repository, and then added a few more enhancements that I either needed for a project (timeline) or wanted to make the building process of websites easier (<code>:user-valid</code> "validation", using <code><label></code> within groups, <code>.row</code> & <code>.row-fluid</code> and the <code>.col-*</code> classes like Bootstrap, <code>.align-*</code> and more.) The demo docs here is the main enhanced that have been added to the <a href="https://picocss.com/">Pico CSS 2.0.6</a> branch, for more docs, refer to the original <a href="https://picocss.com/docs">Pico CSS docs</a>.</p>
|
||||
<p>Some of the demos on this page do require <a href="https://github.com/Yohn/PicoCSS/tree/main/docs/js">Vanilla JavaScript Files</a> to work the same as the preview here. I may get a build script going to compile the javascript plugins / components later. Let me know if this feature would help you.</p>
|
||||
<footer>If this fork has helped you, please <a href="https://github.com/Yohn/PicoCSS">Like</a> this fork!</footer>
|
||||
</article>
|
||||
<!-- Validation -->
|
||||
<form action="javascript:void(0);">
|
||||
<article id="group">
|
||||
<header><h2>Validation</h2></header>
|
||||
<p>
|
||||
Form :user-valid and :user-invalid
|
||||
</p>
|
||||
<input placeholder="name" type="text" name="test" minlength="4" maxlength="30" pattern="[a-z]{4,30}" required>
|
||||
<small data-valid="this is good!" data-invalid="Min of 4 characters, max of 39, only a-z no caps."></small>
|
||||
|
||||
<input placeholder="Url" type="url" name="test" required>
|
||||
<small data-valid="this is good!" data-invalid="Must be a url."></small>
|
||||
|
||||
<input placeholder="Email" type="email" name="test" required>
|
||||
<small data-valid="this is good!" data-invalid="Must be a valid email."></small>
|
||||
|
||||
<select name="favorite-cuisine" required>
|
||||
<option selected disabled value="">Select your favorite cuisine...</option>
|
||||
<option>Italian</option>
|
||||
<option>Japanese</option>
|
||||
<option>Indian</option>
|
||||
<option>Thai</option>
|
||||
<option>French</option>
|
||||
</select>
|
||||
<small data-valid="this is good!" data-invalid="Error.."></small>
|
||||
|
||||
<input type="date" name="date" required>
|
||||
<small data-valid="this is good!" data-invalid="Error.."></small>
|
||||
|
||||
<input type="datetime-local" name="datetime-local" required>
|
||||
<small data-valid="this is good!" data-invalid="Error.."></small>
|
||||
|
||||
<input type="month" name="month" min="2018-03" max="2024-12" placeholder="2024-12" step="1" pattern="[0-9]{4}-[0-9]{2}" required>
|
||||
<small data-valid="this is good!" data-invalid="Format: YYYY-MM"></small>
|
||||
|
||||
<input type="time" name="time" required>
|
||||
<small data-valid="this is good!" data-invalid="Error.."></small>
|
||||
|
||||
<header>Check JS for FileValidator class</header>
|
||||
<input type="file" data-max-size="1048576" accept=".jpg,.svg,.png,.gif,.webp" id="checkFile" multiple />
|
||||
<small data-invalid="Only .jpg,.svg,.png,.gif,.webp image types allowed" data-valid="Good!"></small>
|
||||
|
||||
<textarea minlength="5" placeholder="its always valid?" required></textarea>
|
||||
<small data-valid="this is good!" data-invalid="Min of 5 characters!"></small>
|
||||
<br>
|
||||
<label><input type="checkbox" name="english" required />English </label>
|
||||
<footer>
|
||||
<input type="submit" value="submit" class="btn btn-primary">
|
||||
</footer>
|
||||
</article>
|
||||
</form>
|
||||
<!-- /Validation -->
|
||||
<!-- Group -->
|
||||
<article id="group">
|
||||
<header><h2>Group</h2></header>
|
||||
<form action="javascript:void(0);">
|
||||
<fieldset role="group">
|
||||
<label for="email">Email:</label>
|
||||
<input name="email" type="email" placeholder="Enter your email" autocomplete="email" />
|
||||
<input type="submit" value="Subscribe" />
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<form action="javascript:void(0);">
|
||||
<fieldset role="group">
|
||||
<label for="ex-browser">Browser:</label>
|
||||
<select id="ex-browser" name="ex-browser">
|
||||
<option value="Firefox">Firefox</option>
|
||||
<option value="Chrome">Chrome</option>
|
||||
<option value="Opera">Opera</option>
|
||||
<option value="Safari">Safari</option>
|
||||
</select>
|
||||
<input type="submit" value="Save" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</article>
|
||||
<!-- ./ Group -->
|
||||
<hr>
|
||||
<!-- rows -->
|
||||
<article id="rows">
|
||||
<header><h2>Rows</h2></header>
|
||||
<p>
|
||||
Similar to <a href="https://getbootstrap.com/docs/5.3/layout/columns/" target="_blank">Bootstrap 5.3.3</a>, with the responsive <code>.col-*COL#*</code>, <code>.col-*BREAKPOINT*-*COL#*</code>, <code>.offset-*COL#*</code>, <code>.offset-*BREAKPOINT*-*COL#*</code>.
|
||||
</p>
|
||||
<p>
|
||||
<code>.row</code> has a max width set to the viewport of your <code>xl</code> viewport (1300px by default)</p>
|
||||
<code>.row-fluid</code> max width is 100%.
|
||||
</p>
|
||||
<div class="row-fluid">
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
<div class="col-6"><article class="pico-background-lime-750">col-6</article></div>
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="col-3 offset-1"><article class="pico-background-lime-750">col-3 offset-1</article></div>
|
||||
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
|
||||
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
|
||||
<div class="col-8"><article class="pico-background-lime-750">col-8</article></div>
|
||||
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="col-5"><article class="pico-background-lime-750">col-5</article></div>
|
||||
<div class="col-7"><article class="pico-background-lime-750">col-7</article></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="col-9"><article class="pico-background-lime-750">col-9</article></div>
|
||||
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
|
||||
</div>
|
||||
</article>
|
||||
<!-- /rows -->
|
||||
<hr>
|
||||
<!-- offset -->
|
||||
<article id="row-offsets">
|
||||
<header><h2>Columns with offsets defined by breakpoints</h2></header>
|
||||
<p>When defining multiple <code>.col-*-*</code> on the same element, the order your put the classes on the element matter.</p>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="col-2 col-md-4"><article class="pico-background-lime-750">col-2, col-md-4</article></div>
|
||||
<div class="col-4 offset-8 col-md-6 offset-md-6"><article class="pico-background-lime-750">col-4, offset-8, col-md-6, offset-md-6</article></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="col-5 offset-1 col-md-11"><article class="pico-background-lime-550">col-5, offset-1, col-md-11</article></div>
|
||||
<div class="col-2 col-md-6"><article class="pico-background-lime-550">col-2, col-md-6</article></div>
|
||||
<div class="col-3 offset-9 col-md-6 offset-md-6"><article class="pico-background-lime-550">col-3, offset-9, col-md-6, offset-md-6</article></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid align-center">
|
||||
<div class="col-3 col-md-12 col-lg-6"><article class="pico-background-lime-850">col-3 col-md-12 col-lg-6</article></div>
|
||||
<div class="col-3 col-md-12 col-lg-6"><article class="pico-background-lime-850">col-3 col-md-12 col-lg-6</article></div>
|
||||
<div class="col-4 col-md-6 col-lg-3"><article class="pico-background-lime-850">col-4 col-md-6 col-lg-3</article></div>
|
||||
<div class="col-6 offset-6 col-md-3 offset-md-9 col-lg-2 offset-lg-10"><article class="pico-background-lime-850">col-6 offset-6 col-md-3 offset-md-9 col-lg-2 offset-lg-10</article></div>
|
||||
</div>
|
||||
</article>
|
||||
<!-- /row-offsets -->
|
||||
<hr>
|
||||
<!-- row-alignments -->
|
||||
<article id="row-alignments">
|
||||
<header><h2>Alignment</h2></header>
|
||||
<p>
|
||||
You can add <code>.align-start</code> (default), <code>.align-center</code> or <code>.align-end</code> to your <code>.row</code> or <code>.row-fluid</code> element to align the containing elements to the top, middle, or bottom.</p>
|
||||
|
||||
<div class="row-fluid align-center" id="row-align-example">
|
||||
<div class="col-4"><article class="pico-background-lime-850">col-4</article></div>
|
||||
<div class="col-4">
|
||||
<article class="pico-background-lime-850">
|
||||
<input type="button" class="change-row-align" data-align="start" value=".align-start">
|
||||
<input type="button" class="change-row-align" data-align="center" value=".align-center">
|
||||
<input type="button" class="change-row-align" data-align="end" value=".align-end">
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-4"><article class="pico-background-lime-850">col-4</article></div>
|
||||
</div>
|
||||
</article>
|
||||
<!-- /row-alignments -->
|
||||
<hr>
|
||||
<!-- row-breakpoints -->
|
||||
<article id="row-breakpoints">
|
||||
<header><h2><code> -md- </code> Breakpoints</h2></header>
|
||||
<div class="row-fluid">
|
||||
<div class="col-12 col-md-4"><article class="pico-background-lime-650">col-12, col-md-4</article></div>
|
||||
<div class="col-12 col-md-4"><article class="pico-background-lime-650">col-12, col-md-4</article></div>
|
||||
<div class="col-12 col-md-4"><article class="pico-background-lime-650">col-12, col-md-4</article></div>
|
||||
</div>
|
||||
</article>
|
||||
<!-- ./ row-breakpoints -->
|
||||
<hr>
|
||||
<!-- Modal -->
|
||||
<article id="modal">
|
||||
<header><h2>Modal</h2></header>
|
||||
<button class="contrast" data-target="modal-example" onclick="toggleModal(event)">
|
||||
Launch demo modal
|
||||
</button>
|
||||
</article>
|
||||
<!-- ./ Modal -->
|
||||
<hr>
|
||||
<!-- notifications -->
|
||||
<article id="notifications">
|
||||
<header><h2>Notificaton</h2></header>
|
||||
<button onclick="showNotification('Accessible short notice.')">
|
||||
Short Notificaton
|
||||
</button>
|
||||
|
||||
<button onclick="showNotification({ html: `
|
||||
<section>
|
||||
<h2>Many news!</h2>
|
||||
<p>This is fantastic!</p>
|
||||
<button onclick='closeNotification()'>OK</button>
|
||||
</section>
|
||||
`, delay: 60 * 1000 })">
|
||||
Big Notificaton
|
||||
</button>
|
||||
|
||||
<!-- accessible dialog based notification element -->
|
||||
<dialog role="alert" data-backdrop="false"></dialog>
|
||||
</article>
|
||||
<!-- /notifications -->
|
||||
<hr>
|
||||
<!-- Accordions -->
|
||||
<article id="accordions">
|
||||
<header><h2>Accordions</h2></header>
|
||||
<details name="example-accordion">
|
||||
<summary>Accordion 1</summary>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque urna diam,
|
||||
tincidunt nec porta sed, auctor id velit. Etiam venenatis nisl ut orci consequat, vitae
|
||||
tempus quam commodo. Nulla non mauris ipsum. Aliquam eu posuere orci. Nulla convallis
|
||||
lectus rutrum quam hendrerit, in facilisis elit sollicitudin. Mauris pulvinar pulvinar
|
||||
mi, dictum tristique elit auctor quis. Maecenas ac ipsum ultrices, porta turpis sit
|
||||
amet, congue turpis.
|
||||
</p>
|
||||
</details>
|
||||
<details name="example-accordion" open>
|
||||
<summary>Accordion 2</summary>
|
||||
<ul>
|
||||
<li>Vestibulum id elit quis massa interdum sodales.</li>
|
||||
<li>Nunc quis eros vel odio pretium tincidunt nec quis neque.</li>
|
||||
<li>Quisque sed eros non eros ornare elementum.</li>
|
||||
<li>Cras sed libero aliquet, porta dolor quis, dapibus ipsum.</li>
|
||||
</ul>
|
||||
</details>
|
||||
</article>
|
||||
<!-- ./ Accordions -->
|
||||
<hr>
|
||||
<article id="timeline" style="background-color: transparent;">
|
||||
<header>
|
||||
<h2>Timeline</h2>
|
||||
<br>
|
||||
<details>
|
||||
<summary>View Custom CSS</summary>
|
||||
<div style="background-color: var(--pico-muted-border-color); padding: .5rem;">
|
||||
--pico-timeline-line-color: var(--pico-primary-background);<br>
|
||||
--pico-timeline-dot-background-color: var(--pico-primary-inverse);<br>
|
||||
--pico-timeline-dot-border-color: var(--pico-primary-background);<br>
|
||||
--pico-timeline-arrow-color: var(--pico-card-sectioning-background-color);
|
||||
</div>
|
||||
</details>
|
||||
</header>
|
||||
<div class="timeline">
|
||||
<section class="point left">
|
||||
<article>
|
||||
<header><h3>2017</h3></header>
|
||||
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
|
||||
</article>
|
||||
</section>
|
||||
<section class="point right">
|
||||
<article>
|
||||
<header><h3>2016</h3></header>
|
||||
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
|
||||
</article>
|
||||
</section>
|
||||
<section class="point left">
|
||||
<article>
|
||||
<header><h3>2015</h3></header>
|
||||
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
|
||||
</article>
|
||||
</section>
|
||||
<section class="point right">
|
||||
<article>
|
||||
<header><h3>2012</h3></header>
|
||||
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="container">
|
||||
<small>
|
||||
Built with <a href="https://picocss.com">Pico</a> •
|
||||
<a href="https://github.com/Yohn/PicoCSS">
|
||||
Source code
|
||||
</a>
|
||||
</small>
|
||||
</footer>
|
||||
<!-- ./ Footer -->
|
||||
|
||||
<!-- Modal example -->
|
||||
<dialog id="modal-example">
|
||||
<article>
|
||||
<header>
|
||||
<button aria-label="Close" rel="prev" data-target="modal-example" onclick="toggleModal(event)"></button>
|
||||
<h3>Confirm your action!</h3>
|
||||
</header>
|
||||
<p>
|
||||
Cras sit amet maximus risus. Pellentesque sodales odio sit amet augue finibus
|
||||
pellentesque. Nullam finibus risus non semper euismod.
|
||||
</p>
|
||||
<footer>
|
||||
<button role="button" class="secondary" data-target="modal-example" onclick="toggleModal(event)">Cancel</button>
|
||||
<button autofocus data-target="modal-example" onclick="toggleModal(event)">
|
||||
Confirm
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
<!-- ./ Modal example -->
|
||||
|
||||
<!-- Minimal theme switcher -->
|
||||
<script src="js/MinimalThemeSwitcher.js"></script>
|
||||
|
||||
<!-- Modal -->
|
||||
<script src="js/Modal.js"></script>
|
||||
<!-- alert notifications -->
|
||||
<script src="js/Notifications.js"></script>
|
||||
<script src="js/FileValidator.js"></script>
|
||||
<script src="js/Accordion.js"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const checkFile = document.getElementById("checkFile");
|
||||
new FileValidator(checkFile);
|
||||
|
||||
const alignButtons = document.querySelectorAll(".change-row-align");
|
||||
const alignRow = document.getElementById("row-align-example");
|
||||
alignButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
let dir = button.getAttribute('data-align');
|
||||
console.log(dir)
|
||||
if(dir == 'start'){
|
||||
alignRow.classList.remove('align-center', 'align-end');
|
||||
alignRow.classList.add('align-start');
|
||||
} else if(dir == 'center'){
|
||||
alignRow.classList.remove('align-start', 'align-end');
|
||||
alignRow.classList.add('align-center');
|
||||
} else {
|
||||
alignRow.classList.remove('align-start', 'align-center');
|
||||
alignRow.classList.add('align-end');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
35
docs/js/Accordion.js
Normal file
35
docs/js/Accordion.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll('details').forEach(el => {
|
||||
let anim = null, opening = false, closing = false
|
||||
let summ = el.querySelector('summary')
|
||||
const runAnim = (height, targetHeight, callback) => {
|
||||
anim?.cancel()
|
||||
anim = el.animate({ height: [`${height}px`,`${targetHeight}px`] },{
|
||||
duration: 400,
|
||||
easing: 'ease-out'
|
||||
})
|
||||
anim.onfinish = () => {
|
||||
anim = null
|
||||
el.style.height = el.style.overflow = ''
|
||||
opening = closing = false
|
||||
callback?.()
|
||||
}
|
||||
}
|
||||
|
||||
summ.addEventListener('click', ev => {
|
||||
ev.preventDefault()
|
||||
el.style.overflow = 'hidden'
|
||||
|
||||
if(!el.open || closing) {
|
||||
el.style.height = `${el.offsetHeight}px`
|
||||
el.open = opening = true
|
||||
runAnim(el.offsetHeight, [...el.children].reduce((a,c) => a+c.offsetHeight, 0))
|
||||
anim.oncancel = () => opening = false
|
||||
} else if(el.open || opening) {
|
||||
closing = true
|
||||
runAnim(el.offsetHeight, summ.offsetHeight, () => el.open = false)
|
||||
anim.oncancel = () => closing = false
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
157
docs/js/FileValidator.js
Normal file
157
docs/js/FileValidator.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
class FileValidator {
|
||||
/**
|
||||
* Initializes the FileValidator with a file input element.
|
||||
* @param {HTMLInputElement} fileInput - File input element to validate.
|
||||
* @param {boolean} displayList - Whether to display the file list (default: true).
|
||||
* @param {HTMLElement} customListContainer - Optional custom container for the file list.
|
||||
*/
|
||||
constructor(fileInput, displayList = true, customListContainer = null) {
|
||||
if (!(fileInput instanceof HTMLInputElement) || fileInput.type !== "file") {
|
||||
throw new Error("FileValidator requires an input element of type 'file'.");
|
||||
}
|
||||
|
||||
this.fileInput = fileInput;
|
||||
this.displayList = displayList;
|
||||
this.customListContainer = customListContainer;
|
||||
|
||||
this.fileInput.addEventListener("change", () => this.validateFiles());
|
||||
|
||||
// Create a container for the file list
|
||||
if (this.displayList) {
|
||||
this.fileListContainer = document.createElement("ul");
|
||||
this.fileListContainer.className = "file-list";
|
||||
if (this.customListContainer) {
|
||||
this.customListContainer.append(this.fileListContainer);
|
||||
} else {
|
||||
this.fileInput.insertAdjacentElement("afterend", this.fileListContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates files based on the accept and size attributes of the input element.
|
||||
* Displays a file list if files are valid.
|
||||
* @returns {boolean} - True if all files are valid, otherwise false.
|
||||
*/
|
||||
validateFiles() {
|
||||
const accept = this.fileInput.getAttribute("accept") || "";
|
||||
const maxSize = parseInt(this.fileInput.getAttribute("data-max-size"), 10) || Infinity;
|
||||
const files = Array.from(this.fileInput.files);
|
||||
|
||||
// Clear any existing list
|
||||
if (this.displayList) {
|
||||
this.fileListContainer.innerHTML = "";
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log("No files selected.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (accept && !this.isFileTypeValid(file, accept)) {
|
||||
console.error(`Invalid file type: ${file.name}`);
|
||||
this.fileInput.setCustomValidity(`Invalid file type: ${file.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.size > maxSize) {
|
||||
console.error(`File too large: ${file.name} (${file.size} bytes)`);
|
||||
this.fileInput.setCustomValidity(`File too large: ${file.name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add file to the list if valid
|
||||
if (this.displayList) {
|
||||
this.addFileToList(file);
|
||||
}
|
||||
}
|
||||
|
||||
this.fileInput.setCustomValidity("");
|
||||
console.log("All files are valid.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the displayed list, including an image preview if the file is an image.
|
||||
* @param {File} file - The file to add to the list.
|
||||
*/
|
||||
addFileToList(file) {
|
||||
const listItem = document.createElement("li");
|
||||
listItem.textContent = `${file.name} (${(file.size / 1024).toFixed(2)} KB)`;
|
||||
|
||||
// Add an image preview if the file is an image
|
||||
if (file.type.startsWith("image/")) {
|
||||
const imagePreview = document.createElement("img");
|
||||
imagePreview.src = URL.createObjectURL(file);
|
||||
imagePreview.style.maxWidth = "50px";
|
||||
imagePreview.style.maxHeight = "50px";
|
||||
imagePreview.onload = () => URL.revokeObjectURL(imagePreview.src); // Release memory
|
||||
listItem.prepend(imagePreview);
|
||||
}
|
||||
|
||||
// Add a remove button
|
||||
const removeButton = document.createElement("button");
|
||||
removeButton.textContent = " ";
|
||||
removeButton.className = "btn-file-rm";
|
||||
removeButton.addEventListener("click", () => this.removeFile(file, listItem));
|
||||
|
||||
listItem.append(removeButton);
|
||||
|
||||
if (this.customListContainer) {
|
||||
this.customListContainer.append(listItem);
|
||||
} else {
|
||||
this.fileListContainer.append(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a file from the list and updates the input file element.
|
||||
* @param {File} file - The file to remove.
|
||||
* @param {HTMLElement} listItem - The list item element to remove.
|
||||
*/
|
||||
removeFile(file, listItem) {
|
||||
const filesArray = Array.from(this.fileInput.files);
|
||||
const updatedFiles = filesArray.filter(f => f !== file);
|
||||
|
||||
// Update the input element with the new FileList
|
||||
const dataTransfer = new DataTransfer();
|
||||
updatedFiles.forEach(f => dataTransfer.items.add(f));
|
||||
this.fileInput.files = dataTransfer.files;
|
||||
|
||||
// Remove the list item from the display
|
||||
listItem.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file's type matches any of the allowed types in the accept attribute.
|
||||
* @param {File} file - File object to check.
|
||||
* @param {string} accept - Accept attribute value (e.g., ".jpg, .png, image/*").
|
||||
* @returns {boolean} - True if the file type is valid, otherwise false.
|
||||
*/
|
||||
isFileTypeValid(file, accept) {
|
||||
const acceptedTypes = accept.split(",").map(type => type.trim());
|
||||
return acceptedTypes.some(type => {
|
||||
if (type.includes("/") && file.type === type) return true;
|
||||
if (type.startsWith(".") && file.name.endsWith(type)) return true;
|
||||
if (type.endsWith("/*")) return file.type.startsWith(type.split("/")[0] + "/");
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Usage example:
|
||||
// <input type="file" id="fileInput" accept=".jpg, .png, .gif, .svg" data-max-size="1048576" multiple>
|
||||
// const fileInput = document.getElementById("fileInput");
|
||||
// new FileValidator(fileInput, false); // Hide display list
|
||||
//
|
||||
// Alternatively, with custom list container:
|
||||
// const customListContainer = document.getElementById("customListContainer");
|
||||
// new FileValidator(fileInput, true, customListContainer);
|
||||
|
||||
// Usage example2:
|
||||
// <input type="file" id="checkfile" accept=".jpg, .png, .gif, .svg" data-max-size="1048576" multiple>
|
||||
// 1048576 = 1MB
|
||||
// const checkfile = document.getElementById("checkfile");
|
||||
// new FileValidator(checkfile);
|
79
docs/js/MinimalThemeSwitcher.js
Normal file
79
docs/js/MinimalThemeSwitcher.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*!
|
||||
* Minimal theme switcher
|
||||
*
|
||||
* Pico.css - https://picocss.com
|
||||
* Copyright 2019-2024 - Licensed under MIT
|
||||
*/
|
||||
|
||||
const themeSwitcher = {
|
||||
// Config
|
||||
_scheme: "auto",
|
||||
menuTarget: "details.dropdown",
|
||||
buttonsTarget: "a[data-theme-switcher]",
|
||||
buttonAttribute: "data-theme-switcher",
|
||||
rootAttribute: "data-theme",
|
||||
localStorageKey: "picoPreferredColorScheme",
|
||||
|
||||
// Init
|
||||
init() {
|
||||
this.scheme = this.schemeFromLocalStorage;
|
||||
this.initSwitchers();
|
||||
},
|
||||
|
||||
// Get color scheme from local storage
|
||||
get schemeFromLocalStorage() {
|
||||
return window.localStorage?.getItem(this.localStorageKey) ?? this._scheme;
|
||||
},
|
||||
|
||||
// Preferred color scheme
|
||||
get preferredColorScheme() {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
},
|
||||
|
||||
// Init switchers
|
||||
initSwitchers() {
|
||||
const buttons = document.querySelectorAll(this.buttonsTarget);
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener(
|
||||
"click",
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
// Set scheme
|
||||
this.scheme = button.getAttribute(this.buttonAttribute);
|
||||
// Close dropdown
|
||||
document.querySelector(this.menuTarget)?.removeAttribute("open");
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
// Set scheme
|
||||
set scheme(scheme) {
|
||||
if (scheme == "auto") {
|
||||
this._scheme = this.preferredColorScheme;
|
||||
} else if (scheme == "dark" || scheme == "light") {
|
||||
this._scheme = scheme;
|
||||
}
|
||||
this.applyScheme();
|
||||
this.schemeToLocalStorage();
|
||||
},
|
||||
|
||||
// Get scheme
|
||||
get scheme() {
|
||||
return this._scheme;
|
||||
},
|
||||
|
||||
// Apply scheme
|
||||
applyScheme() {
|
||||
document.querySelector("html")?.setAttribute(this.rootAttribute, this.scheme);
|
||||
},
|
||||
|
||||
// Store scheme to local storage
|
||||
schemeToLocalStorage() {
|
||||
window.localStorage?.setItem(this.localStorageKey, this.scheme);
|
||||
},
|
||||
};
|
||||
|
||||
// Init
|
||||
themeSwitcher.init();
|
76
docs/js/Modal.js
Normal file
76
docs/js/Modal.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Modal
|
||||
*
|
||||
* Pico.css - https://picocss.com
|
||||
* Copyright 2019-2024 - Licensed under MIT
|
||||
*/
|
||||
//document.addEventListener("DOMContentLoaded", () => {
|
||||
// Config
|
||||
const isOpenClass = "modal-is-open";
|
||||
const openingClass = "modal-is-opening";
|
||||
const closingClass = "modal-is-closing";
|
||||
const scrollbarWidthCssVar = "--pico-scrollbar-width";
|
||||
const animationDuration = 400; // ms
|
||||
let visibleModal = null;
|
||||
|
||||
// Toggle modal
|
||||
const toggleModal = (event) => {
|
||||
event.preventDefault();
|
||||
const modal = document.getElementById(event.currentTarget.dataset.target);
|
||||
if (!modal) return;
|
||||
modal && (modal.open ? closeModal(modal) : openModal(modal));
|
||||
};
|
||||
|
||||
// Open modal
|
||||
const openModal = (modal) => {
|
||||
const { documentElement: html } = document;
|
||||
const scrollbarWidth = getScrollbarWidth();
|
||||
if (scrollbarWidth) {
|
||||
html.style.setProperty(scrollbarWidthCssVar, `${scrollbarWidth}px`);
|
||||
}
|
||||
html.classList.add(isOpenClass, openingClass);
|
||||
setTimeout(() => {
|
||||
visibleModal = modal;
|
||||
html.classList.remove(openingClass);
|
||||
}, animationDuration);
|
||||
modal.showModal();
|
||||
};
|
||||
|
||||
// Close modal
|
||||
const closeModal = (modal) => {
|
||||
visibleModal = null;
|
||||
const { documentElement: html } = document;
|
||||
html.classList.add(closingClass);
|
||||
setTimeout(() => {
|
||||
html.classList.remove(closingClass, isOpenClass);
|
||||
html.style.removeProperty(scrollbarWidthCssVar);
|
||||
modal.close();
|
||||
}, animationDuration);
|
||||
};
|
||||
|
||||
// Close with a click outside
|
||||
document.addEventListener("click", (event) => {
|
||||
if (visibleModal === null) return;
|
||||
const modalContent = visibleModal.querySelector("article");
|
||||
const isClickInside = modalContent.contains(event.target);
|
||||
!isClickInside && closeModal(visibleModal);
|
||||
});
|
||||
|
||||
// Close with Esc key
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && visibleModal) {
|
||||
closeModal(visibleModal);
|
||||
}
|
||||
});
|
||||
|
||||
// Get scrollbar width
|
||||
const getScrollbarWidth = () => {
|
||||
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||
return scrollbarWidth;
|
||||
};
|
||||
|
||||
// Is scrollbar visible
|
||||
const isScrollbarVisible = () => {
|
||||
return document.body.scrollHeight > screen.height;
|
||||
};
|
||||
//})
|
20
docs/js/Notifications.js
Normal file
20
docs/js/Notifications.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
function showNotification(options = {}) {
|
||||
const dialog = document.querySelector("dialog[role='alert']");
|
||||
|
||||
if (options.text || typeof options === "string") {
|
||||
dialog.innerText = options.text || options;
|
||||
} else if (options.html) {
|
||||
dialog.innerHTML = options.html;
|
||||
}
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
setTimeout(() => {
|
||||
dialog.close();
|
||||
}, options.delay || 3000);
|
||||
}
|
||||
|
||||
function closeNotification() {
|
||||
const dialog = document.querySelector("dialog[role='alert']");
|
||||
dialog.close();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue