Refonte design V1
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Comportements communs du back-office (aucune dépendance).
|
||||
* Chaque bloc s'active par un attribut data-* : sans JavaScript, les formulaires fonctionnent quand même.
|
||||
*/
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
// --- Navigation en bandeau (petits écrans) : l'entrée active reste visible ---------------------
|
||||
const current = document.querySelector('.nav .nav-link.active');
|
||||
if (current && current.parentElement.scrollWidth > current.parentElement.clientWidth) {
|
||||
current.parentElement.scrollLeft = current.offsetLeft - 16;
|
||||
}
|
||||
|
||||
// --- Notifications : fermeture manuelle, et automatique après quelques secondes ----------------
|
||||
const dismiss = (toast) => {
|
||||
toast.classList.add('is-leaving');
|
||||
setTimeout(() => toast.remove(), 220);
|
||||
};
|
||||
document.querySelectorAll('[data-toast]').forEach((toast) => {
|
||||
let timer = setTimeout(() => dismiss(toast), 9000);
|
||||
toast.addEventListener('mouseenter', () => clearTimeout(timer));
|
||||
toast.addEventListener('mouseleave', () => { timer = setTimeout(() => dismiss(toast), 4000); });
|
||||
toast.querySelector('[data-dismiss]')?.addEventListener('click', () => { clearTimeout(timer); dismiss(toast); });
|
||||
});
|
||||
|
||||
// --- Confirmation : <form data-confirm="Question ?" data-confirm-label="Supprimer" data-confirm-danger>
|
||||
const dialog = document.getElementById('confirm-dialog');
|
||||
if (dialog && typeof dialog.showModal === 'function') {
|
||||
const message = dialog.querySelector('[data-message]');
|
||||
const ok = dialog.querySelector('[data-ok]');
|
||||
let pending = null;
|
||||
|
||||
document.addEventListener('submit', (e) => {
|
||||
const form = e.target;
|
||||
if (!(form instanceof HTMLFormElement) || !form.dataset.confirm || form.dataset.confirmed) return;
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
pending = form;
|
||||
message.textContent = form.dataset.confirm;
|
||||
ok.textContent = form.dataset.confirmLabel || 'Confirmer';
|
||||
ok.classList.toggle('danger', 'confirmDanger' in form.dataset);
|
||||
ok.classList.toggle('solid', 'confirmDanger' in form.dataset);
|
||||
dialog.returnValue = '';
|
||||
dialog.showModal();
|
||||
}, true);
|
||||
|
||||
dialog.addEventListener('close', () => {
|
||||
const form = pending;
|
||||
pending = null;
|
||||
if (!form || dialog.returnValue !== 'ok') return;
|
||||
form.dataset.confirmed = '1';
|
||||
form.requestSubmit ? form.requestSubmit() : form.submit();
|
||||
});
|
||||
}
|
||||
|
||||
// --- Bouton occupé : <form data-busy="Texte pendant l'envoi…"> ----------------------------------
|
||||
document.addEventListener('submit', (e) => {
|
||||
const form = e.target;
|
||||
if (!(form instanceof HTMLFormElement) || !form.dataset.busy || e.defaultPrevented) return;
|
||||
const button = form.querySelector('button[type=submit], button:not([type])');
|
||||
if (!button) return;
|
||||
button.classList.add('is-busy');
|
||||
button.setAttribute('aria-disabled', 'true');
|
||||
const label = button.querySelector('[data-label]');
|
||||
if (label) label.textContent = form.dataset.busy;
|
||||
});
|
||||
|
||||
// --- Copie dans le presse-papiers : <button data-copy="texte"> ---------------------------------
|
||||
document.addEventListener('click', async (e) => {
|
||||
const button = e.target.closest('[data-copy]');
|
||||
if (!button) return;
|
||||
const label = button.querySelector('[data-label]') || button;
|
||||
const initial = label.textContent;
|
||||
try {
|
||||
await navigator.clipboard.writeText(button.dataset.copy);
|
||||
label.textContent = 'Copié';
|
||||
} catch {
|
||||
label.textContent = 'Copie impossible';
|
||||
}
|
||||
setTimeout(() => { label.textContent = initial; }, 1600);
|
||||
});
|
||||
|
||||
// --- Zone de dépôt : <label class="dropzone" data-dropzone><input type="file"> … ---------------
|
||||
document.querySelectorAll('[data-dropzone]').forEach((zone) => {
|
||||
const input = zone.querySelector('input[type=file]');
|
||||
const name = zone.querySelector('[data-name]');
|
||||
const prompt = zone.querySelector('[data-prompt]');
|
||||
if (!input) return;
|
||||
|
||||
const show = () => {
|
||||
const file = input.files[0];
|
||||
if (name) { name.textContent = file ? file.name : ''; name.hidden = !file; }
|
||||
if (prompt) prompt.hidden = Boolean(file);
|
||||
};
|
||||
input.addEventListener('change', show);
|
||||
|
||||
['dragenter', 'dragover'].forEach((type) => zone.addEventListener(type, (e) => {
|
||||
e.preventDefault();
|
||||
zone.classList.add('is-over');
|
||||
}));
|
||||
['dragleave', 'drop'].forEach((type) => zone.addEventListener(type, () => zone.classList.remove('is-over')));
|
||||
zone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
if (input.disabled || !e.dataTransfer.files.length) return;
|
||||
const transfer = new DataTransfer();
|
||||
transfer.items.add(e.dataTransfer.files[0]);
|
||||
input.files = transfer.files;
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user