feat: add file upload pattern
This commit is contained in:
1 parent
7b733e53d7
commit
4e5d1e32e8
7 files changed
+233
No files matched your search
@@ -847,7 +847,27 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script src="../scripts/help-icon-overlays.js"></script>
|
<script src="../scripts/help-icon-overlays.js"></script>
|
||||||
|
<section id="component-file-input">
|
||||||
|
<p class="sg-preview-label">Component: File Input</p>
|
||||||
|
<div class="sg-form-preview-area">
|
||||||
|
<div class="sg-file-input" data-component="file-input">
|
||||||
|
<label class="sg-label" for="component-file-input-control">Dateien auswählen</label>
|
||||||
|
<p class="sg-file-input__description sg-body" id="component-file-input-description">Mehrere Dateien können ausgewählt werden.</p>
|
||||||
|
<input class="sg-file-input__control" id="component-file-input-control" type="file" multiple aria-describedby="component-file-input-description">
|
||||||
|
<button class="sg-interaction-element sg-button sg-button--active sg-file-input__trigger" type="button">Dateien auswählen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.querySelectorAll('[data-component="file-input"]').forEach((fileInput) => {
|
||||||
|
const input = fileInput.querySelector('.sg-file-input__control');
|
||||||
|
const trigger = fileInput.querySelector('.sg-file-input__trigger');
|
||||||
|
if (input && trigger) {
|
||||||
|
trigger.addEventListener('click', () => input.click());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.sg-mode-toggle:not(.sg-activation-mode-toggle)').forEach((toggle) => {
|
document.querySelectorAll('.sg-mode-toggle:not(.sg-activation-mode-toggle)').forEach((toggle) => {
|
||||||
toggle.addEventListener('click', () => {
|
toggle.addEventListener('click', () => {
|
||||||
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
|
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
<li><a href="./patterns/content-cards-group.html">Content Cards Group</a></li>
|
<li><a href="./patterns/content-cards-group.html">Content Cards Group</a></li>
|
||||||
<li><a href="./patterns/card-gruppe-mit-tastennavigation.html">Card Gruppe mit Tastennavigation</a></li>
|
<li><a href="./patterns/card-gruppe-mit-tastennavigation.html">Card Gruppe mit Tastennavigation</a></li>
|
||||||
<li><a href="./patterns/formular-mit-abschnitten.html">Formular mit Abschnitten</a></li>
|
<li><a href="./patterns/formular-mit-abschnitten.html">Formular mit Abschnitten</a></li>
|
||||||
|
<li><a href="./patterns/file-upload.html">Datei-Upload</a></li>
|
||||||
<li><a href="./patterns/multiselektions-pulldown.html">Multiselektions-Pulldown</a></li>
|
<li><a href="./patterns/multiselektions-pulldown.html">Multiselektions-Pulldown</a></li>
|
||||||
<li><a href="./patterns/text-layouts.html">Text Layouts</a></li>
|
<li><a href="./patterns/text-layouts.html">Text Layouts</a></li>
|
||||||
<li><a href="./patterns/delete-confirmation-overlay.html">Overlay Card</a></li>
|
<li><a href="./patterns/delete-confirmation-overlay.html">Overlay Card</a></li>
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Styleguide – Pattern Datei-Upload</title>
|
||||||
|
<link rel="stylesheet" href="../styleguide.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="sg-main-heading">Pattern – Datei-Upload</h1>
|
||||||
|
<section id="pattern-file-upload">
|
||||||
|
<p class="sg-preview-label">Pattern: Datei-Upload</p>
|
||||||
|
<form class="sg-file-upload" data-pattern="file-upload" action="#" method="post">
|
||||||
|
<div>
|
||||||
|
<label class="sg-label" for="file-upload-input">Dokumente hochladen</label>
|
||||||
|
<p class="sg-body sg-file-upload__description" id="file-upload-help">Mehrere Dateien auswählen. Erlaubte Formate: PDF, DOCX. Maximale Dateigröße: 10 MB pro Datei.</p>
|
||||||
|
</div>
|
||||||
|
<div class="sg-file-upload__dropzone" data-drag-over="false">
|
||||||
|
<input class="sg-file-input__control" id="file-upload-input" name="documents" type="file" multiple aria-describedby="file-upload-help file-upload-status">
|
||||||
|
<p class="sg-body sg-file-upload__prompt">Dateien hierher ziehen oder</p>
|
||||||
|
<button class="sg-interaction-element sg-button sg-button--active sg-file-upload__trigger" type="button">Dateien auswählen</button>
|
||||||
|
</div>
|
||||||
|
<p class="sg-body sg-file-upload__status" id="file-upload-status" aria-live="polite">Noch keine Dateien ausgewählt.</p>
|
||||||
|
<ul class="sg-file-upload__files" aria-label="Ausgewählte Dateien"></ul>
|
||||||
|
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="submit" disabled aria-disabled="true">Hochladen</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const upload = document.querySelector('[data-pattern="file-upload"]');
|
||||||
|
if (!upload) return;
|
||||||
|
const input = upload.querySelector('#file-upload-input');
|
||||||
|
const dropzone = upload.querySelector('.sg-file-upload__dropzone');
|
||||||
|
const trigger = upload.querySelector('.sg-file-upload__trigger');
|
||||||
|
const filesList = upload.querySelector('.sg-file-upload__files');
|
||||||
|
const status = upload.querySelector('.sg-file-upload__status');
|
||||||
|
const submit = upload.querySelector('[type="submit"]');
|
||||||
|
let selectedFiles = [];
|
||||||
|
const formatSize = (bytes) => bytes < 1024 * 1024 ? `${Math.max(1, Math.round(bytes / 1024))} KB` : `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||||
|
const render = () => {
|
||||||
|
filesList.innerHTML = '';
|
||||||
|
selectedFiles.forEach((file, index) => {
|
||||||
|
const item = document.createElement('li');
|
||||||
|
item.className = 'sg-file-upload__file';
|
||||||
|
const name = document.createElement('span');
|
||||||
|
name.className = 'sg-file-upload__file-name';
|
||||||
|
name.textContent = file.name;
|
||||||
|
const meta = document.createElement('span');
|
||||||
|
meta.className = 'sg-file-upload__file-meta';
|
||||||
|
meta.textContent = ` · ${formatSize(file.size)}`;
|
||||||
|
name.append(meta);
|
||||||
|
const remove = document.createElement('button');
|
||||||
|
remove.className = 'sg-interaction-element sg-button sg-button--active sg-file-upload__remove';
|
||||||
|
remove.type = 'button';
|
||||||
|
remove.textContent = 'Entfernen';
|
||||||
|
remove.addEventListener('click', () => { selectedFiles.splice(index, 1); render(); });
|
||||||
|
item.append(name, remove);
|
||||||
|
filesList.append(item);
|
||||||
|
});
|
||||||
|
const count = selectedFiles.length;
|
||||||
|
status.textContent = count === 0 ? 'Noch keine Dateien ausgewählt.' : `${count} Datei${count === 1 ? '' : 'en'} ausgewählt.`;
|
||||||
|
submit.disabled = count === 0;
|
||||||
|
submit.setAttribute('aria-disabled', String(count === 0));
|
||||||
|
submit.classList.toggle('sg-button--process-inactive', count === 0);
|
||||||
|
};
|
||||||
|
trigger.addEventListener('click', () => input.click());
|
||||||
|
input.addEventListener('change', () => { selectedFiles = [...selectedFiles, ...Array.from(input.files)]; input.value = ''; render(); });
|
||||||
|
['dragenter', 'dragover'].forEach((eventName) => dropzone.addEventListener(eventName, (event) => { event.preventDefault(); dropzone.dataset.dragOver = 'true'; }));
|
||||||
|
['dragleave', 'drop'].forEach((eventName) => dropzone.addEventListener(eventName, (event) => { event.preventDefault(); dropzone.dataset.dragOver = 'false'; }));
|
||||||
|
dropzone.addEventListener('drop', (event) => { selectedFiles = [...selectedFiles, ...Array.from(event.dataTransfer.files)]; render(); });
|
||||||
|
render();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -148,6 +148,14 @@
|
|||||||
<tr><td>layout-delete-confirmation-target-dim-opacity</td><td>opacity-delete-confirmation-target-dim</td><td>Deckkraft der grauen Überlagerung auf dem zu löschenden Objekt.</td></tr>
|
<tr><td>layout-delete-confirmation-target-dim-opacity</td><td>opacity-delete-confirmation-target-dim</td><td>Deckkraft der grauen Überlagerung auf dem zu löschenden Objekt.</td></tr>
|
||||||
<tr><td>layer-delete-confirmation-overlay</td><td>dimension-layer-delete-confirmation-overlay</td><td>Z-Index-Layer des schwebenden Bestätigungsfensters.</td></tr>
|
<tr><td>layer-delete-confirmation-overlay</td><td>dimension-layer-delete-confirmation-overlay</td><td>Z-Index-Layer des schwebenden Bestätigungsfensters.</td></tr>
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
|
|
||||||
|
<h3 class="sg-sub-heading sg-section-h3">Datei-Upload</h3>
|
||||||
|
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck im Pattern</th></tr></thead><tbody>
|
||||||
|
<tr><td>surface-file-upload-dropzone</td><td>surface-form-preview</td><td>Hintergrundfläche des auswählbaren Upload-Bereichs.</td></tr>
|
||||||
|
<tr><td>surface-file-upload-file-item</td><td>surface-control-default</td><td>Hintergrundfläche eines Eintrags in der Dateiliste.</td></tr>
|
||||||
|
<tr><td>text-file-upload-meta</td><td>text-control-disabled</td><td>Textfarbe für Dateigröße und ergänzende Dateimetadaten.</td></tr>
|
||||||
|
<tr><td>text-file-upload-error</td><td>color-signal-red</td><td>Textfarbe für Validierungs- und Upload-Fehlermeldungen.</td></tr>
|
||||||
|
</tbody></table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@import "./styles/02-base.css";
|
@import "./styles/02-base.css";
|
||||||
@import "./styles/03-typography-helpers.css";
|
@import "./styles/03-typography-helpers.css";
|
||||||
@import "./styles/10-components-interactive-elements.css";
|
@import "./styles/10-components-interactive-elements.css";
|
||||||
|
@import "./styles/47-patterns-file-upload.css";
|
||||||
@import "./styles/20-patterns-portal-header.css";
|
@import "./styles/20-patterns-portal-header.css";
|
||||||
@import "./styles/21-patterns-options-row.css";
|
@import "./styles/21-patterns-options-row.css";
|
||||||
@import "./styles/22-patterns-object-card.css";
|
@import "./styles/22-patterns-object-card.css";
|
||||||
|
|||||||
@@ -1585,3 +1585,45 @@
|
|||||||
color: var(--text-hyperlink);
|
color: var(--text-hyperlink);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Component: File Input */
|
||||||
|
.sg-file-input {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-small);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-input__control {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-input:focus-within .sg-file-input__trigger {
|
||||||
|
outline: 2px solid var(--text-hyperlink);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-input__description {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-control-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-input__trigger {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-input--disabled {
|
||||||
|
opacity: var(--disabled-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-input--disabled .sg-file-input__trigger {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/* ========================================================= */
|
||||||
|
/* Patterns: Datei-Upload */
|
||||||
|
/* ========================================================= */
|
||||||
|
|
||||||
|
.sg-file-upload {
|
||||||
|
--surface-file-upload-dropzone: var(--surface-form-preview);
|
||||||
|
--surface-file-upload-file-item: var(--surface-control-default);
|
||||||
|
--text-file-upload-meta: var(--text-control-disabled);
|
||||||
|
--text-file-upload-error: var(--color-signal-red);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-large);
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--layout-input-field-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__description,
|
||||||
|
.sg-file-upload__status,
|
||||||
|
.sg-file-upload__file-meta {
|
||||||
|
margin: var(--spacing-small) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__dropzone {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-small);
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
background: var(--surface-file-upload-dropzone);
|
||||||
|
border: 1px dashed var(--surface-button-active);
|
||||||
|
border-radius: var(--radius-interaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__dropzone[data-drag-over="true"] {
|
||||||
|
outline: 2px solid var(--text-hyperlink);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__prompt {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__files {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-small);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__file {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--spacing-large);
|
||||||
|
padding: var(--spacing-small) var(--spacing-large);
|
||||||
|
color: var(--text-control-default);
|
||||||
|
background: var(--surface-file-upload-file-item);
|
||||||
|
border-radius: var(--radius-interaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__file-name {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__file-meta {
|
||||||
|
color: var(--text-file-upload-meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__remove {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-file-upload__error {
|
||||||
|
color: var(--text-file-upload-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 48rem) {
|
||||||
|
.sg-file-upload__file {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user