Compare commits

..

137 Commits

Author SHA1 Message Date
gitea_admin ee8f4225a7 Sync styleguide 2026.05.18.1 2026-06-19 12:46:43 +02:00
gitea_admin 821b480ecc Sync styleguide 2026.05.18.1 2026-06-18 12:22:31 +02:00
gitea_admin 674fe23c61 Sync styleguide 2026.05.18.1 2026-06-18 12:17:03 +02:00
gitea_admin 499cc25e7e Sync styleguide 2026.05.18.1 2026-06-18 10:56:17 +02:00
gitea_admin 07e4253d36 Sync styleguide 2026.05.18.1 2026-06-18 10:48:09 +02:00
gitea_admin dd245b178c Hide order number on mobile 2026-06-17 14:25:13 +02:00
gitea_admin 44dc940851 Fix mobile bestellungen close button 2026-06-17 14:24:23 +02:00
gitea_admin ee8b2516e5 Use default mobile detail inset 2026-06-17 14:23:33 +02:00
gitea_admin eaf90df3a2 Match mobile detail background 2026-06-17 14:22:22 +02:00
gitea_admin a277cacf3a Fix mobile bestellungen detail host 2026-06-17 14:21:24 +02:00
gitea_admin 3129f02f1d Trim mobile bestellungen detail 2026-06-17 14:18:35 +02:00
gitea_admin c1d4f6d1e9 Add mobile bestellungen detail view 2026-06-17 14:17:17 +02:00
gitea_admin 818bca5aff Make order name open drawer 2026-06-17 14:11:57 +02:00
gitea_admin c30a3b23f3 Fix bestellungen cell clipping 2026-06-17 14:08:49 +02:00
gitea_admin 0edb9b4038 Align bestellungen columns left 2026-06-17 14:07:18 +02:00
gitea_admin 969da3df9d Fix bestellungen price column 2026-06-17 14:06:07 +02:00
gitea_admin 5a2cf296e6 Fix bestellungen search field alignment 2026-06-17 14:03:24 +02:00
gitea_admin f7165ddcb4 Keep bestellungen clear button inside field 2026-06-17 13:56:15 +02:00
gitea_admin 6af076b3df Right-align bestellungen search field 2026-06-17 13:48:22 +02:00
gitea_admin 3cd2ced365 Sync styleguide 2026.05.18.1 2026-06-17 13:45:35 +02:00
gitea_admin cec14b71f6 Reduce mobile bestellungen search width 2026-06-17 13:39:42 +02:00
gitea_admin 93699ed502 Fix logout redirect target 2026-06-17 13:32:31 +02:00
gitea_admin e4eff07fd0 Revert recent repo changes 2026-06-16 20:53:56 +02:00
gitea_admin 0ee3e7bdf3 Revert Bestellungen portal CSS overrides 2026-06-16 20:53:18 +02:00
gitea_admin c3ac69524a Move Bestellungen table overrides to portal CSS 2026-06-16 20:44:48 +02:00
gitea_admin 98b179e924 Sync styleguide 2026.05.18.1 2026-06-16 20:42:20 +02:00
gitea_admin 887ff2a90e Fix logout redirect 2026-06-16 20:33:04 +02:00
gitea_admin 72109be6f1 Menueintraege Produkte und Artikel 2026-06-16 20:29:47 +02:00
gitea_admin 5aa7aee2d6 Tighten Bestellungen total column 2026-06-16 20:20:08 +02:00
gitea_admin 1e26805f51 Show billing address name in Bestellungen 2026-06-16 20:15:31 +02:00
gitea_admin 557f1793eb Keep Bestellungen search focus 2026-06-16 20:13:19 +02:00
gitea_admin 0ea233f5ee Fix Bestellungen search filtering 2026-06-16 20:10:55 +02:00
gitea_admin 9e99060575 Add OTC credit card payment method 2026-06-16 20:05:52 +02:00
gitea_admin 3acca065b1 Stop OTC live validation scrolling 2026-06-16 20:02:24 +02:00
gitea_admin b1f6e17966 Fix Bestellungen search fragment request 2026-06-16 20:00:41 +02:00
gitea_admin a463632772 Add SSE live updates for bestellungen 2026-06-16 19:43:19 +02:00
gitea_admin c0f819fd3d Map order payload to addresses 2026-06-16 19:26:58 +02:00
gitea_admin c410fae2e9 Bestellungen-Drawer im Frontend sichtbar 2026-06-16 18:43:52 +02:00
gitea_admin 26d553385f Send only order number to Excel webhook 2026-06-16 18:43:33 +02:00
gitea_admin dd8184c2b9 Bestellungen-Drawer mit Backdrop 2026-06-16 18:41:24 +02:00
gitea_admin eee2b87a48 Send invoice data to Excel webhook 2026-06-16 18:38:33 +02:00
gitea_admin a814fbafcc Fix OTC order date validation 2026-06-16 18:07:19 +02:00
gitea_admin 4a5ce4f5c6 Align OTC webhook dispatch 2026-06-16 18:03:21 +02:00
gitea_admin 179287566d Fix OTC submit feedback 2026-06-16 18:01:17 +02:00
gitea_admin e8f4b2ac6a Fix direct order insert 2026-06-16 15:42:38 +02:00
gitea_admin 0ebd635155 Generate direct order refs 2026-06-16 15:40:46 +02:00
gitea_admin e45b065c37 Remove direct Excel webhook helper 2026-06-16 15:38:48 +02:00
gitea_admin c760f41247 Restore OTC webhook trigger 2026-06-16 15:38:36 +02:00
gitea_admin 862f0a3e8e Excel webhook sends throttled 2026-06-16 15:33:52 +02:00
gitea_admin 57dfae624d Zentrale Excel-Webhook-Zustellung 2026-06-16 15:16:02 +02:00
gitea_admin 53d8cd7339 Fix left nav menu alignment 2026-06-16 15:14:39 +02:00
gitea_admin eb010e542d Fix OTC overlay text selection 2026-06-16 15:12:18 +02:00
gitea_admin dd4c46768f Add OTC order date picker 2026-06-16 15:08:56 +02:00
gitea_admin 3dd5f5f1b4 Sync styleguide 2026.05.18.1 2026-06-16 14:22:28 +02:00
gitea_admin c1b64d048e Sync styleguide 2026.05.18.1 2026-06-16 11:30:30 +02:00
gitea_admin b37b0accb7 Sync styleguide 2026.05.18.1 2026-06-16 11:00:56 +02:00
gitea_admin 593dbe72c3 Sync styleguide 2026.05.18.1 2026-06-16 10:53:21 +02:00
gitea_admin a9be216565 Sync styleguide 2026.05.18.1 2026-06-16 09:41:54 +02:00
gitea_admin a6dd308f85 Sync styleguide 2026.05.18.1 2026-06-16 08:45:05 +02:00
gitea_admin 68449f1d0f Sync styleguide 2026.05.18.1 2026-06-16 07:05:19 +02:00
gitea_admin e91e4bcbf3 Sync styleguide 2026.05.18.1 2026-06-15 17:56:24 +02:00
gitea_admin c142fc9f74 Bestellungen-Suche wieder auf Fragment laden 2026-06-15 17:06:10 +02:00
gitea_admin 05ca584e45 Bestellungen-Suche auf Backend-GET umstellen 2026-06-15 17:00:47 +02:00
gitea_admin 76ac5aada8 Bestellungen-Suche wieder auf Fragment laden 2026-06-15 16:48:50 +02:00
gitea_admin 5080b58db5 Sync styleguide 2026.05.18.1 2026-06-15 16:36:54 +02:00
gitea_admin 8e40099dce Bestellungen-Suche auf Server-Navigation umstellen 2026-06-15 16:28:11 +02:00
gitea_admin 034f16b4d0 Bestellungen-Suche per Delegation absichern 2026-06-15 16:22:52 +02:00
gitea_admin 50a1b5aa77 Bestellungen-Suche auf alle Felder erweitern 2026-06-15 16:19:17 +02:00
gitea_admin 27971bde63 Bestellungen-Suche und Name-Spalte fixen 2026-06-15 15:09:27 +02:00
gitea_admin f8a468a4f9 Sync styleguide 2026.05.18.1 2026-06-15 15:05:44 +02:00
gitea_admin 795afc2ecb Bestellungen-Suche auf Fragment-Reload umstellen 2026-06-15 15:05:22 +02:00
gitea_admin 5b4b03ef77 Abstand ueber Bestellungen-Tabelle setzen 2026-06-15 14:58:16 +02:00
gitea_admin 1aece3bc19 Bestellnummer als Link ohne Buttonrahmen 2026-06-15 14:57:37 +02:00
gitea_admin 78ffb18aed Bestellungen-Tabelle im Content initialisieren 2026-06-15 14:55:45 +02:00
gitea_admin fe7e7b6575 Bestellliste im ERP-Home ergänzen 2026-06-15 14:54:07 +02:00
gitea_admin 359dd2cbe3 OTC-Billing-Defaults wiederherstellen 2026-06-15 14:34:47 +02:00
gitea_admin f3ec2808f3 OTC-Reset nur fuer Produkte und Preis 2026-06-15 14:32:21 +02:00
gitea_admin c2aef4fa0d OTC-Overlay-Defaults und Buttonzustand korrigieren 2026-06-15 14:25:00 +02:00
gitea_admin dcded917a9 OTC API-Endpunkt korrigieren 2026-06-15 14:17:42 +02:00
gitea_admin fb812e147e OTC auf Produkt-ID umstellen 2026-06-15 14:05:29 +02:00
gitea_admin 82387690dc OTC-Produktreihenfolge angleichen 2026-06-15 14:02:06 +02:00
gitea_admin e4277b1867 Sync styleguide 2026.05.18.1 2026-06-15 13:59:31 +02:00
gitea_admin 2a6487aae9 Load overlay helper globally 2026-06-15 13:55:32 +02:00
gitea_admin b9cd92e221 OTC-Submit-Reset korrigieren 2026-06-15 13:48:52 +02:00
gitea_admin 69a9420c03 OTC-Overlay im Home 2026-06-15 13:29:20 +02:00
gitea_admin 457fa0316f Adjust bestellungen card spacing 2026-06-15 13:08:47 +02:00
gitea_admin fdc50ff61b Fix bestellungen card spacing 2026-06-15 13:04:28 +02:00
gitea_admin 2b990c4b0e Expand bestellungen basic card 2026-06-15 12:54:33 +02:00
gitea_admin 49210bce01 Add OTC order basic card 2026-06-15 12:52:21 +02:00
gitea_admin 85db0620c7 Wrap main heading in transparent card 2026-06-15 12:47:59 +02:00
gitea_admin 42d60faf27 Add content card title by submenu 2026-06-15 12:43:27 +02:00
gitea_admin 612f6b2a28 Update heading by selected module 2026-06-15 12:42:15 +02:00
gitea_admin af9315690b Link left nav to top modules 2026-06-15 12:40:35 +02:00
gitea_admin 3621bf4388 Add overview tab to portal header 2026-06-15 12:38:12 +02:00
gitea_admin 91d9616a5b Remove portal filter card 2026-06-15 12:36:49 +02:00
gitea_admin 6052240d5e Rename top modules in portal header 2026-06-15 12:36:06 +02:00
gitea_admin 24c98f8904 Rename portal override stylesheet 2026-06-15 12:34:43 +02:00
gitea_admin 5256f22abe Move portal overrides to separate stylesheet 2026-06-15 12:33:33 +02:00
gitea_admin 49db102f2e Use global tab colors in portal header 2026-06-15 12:31:44 +02:00
gitea_admin e3e027e419 Darken inactive portal header tabs 2026-06-15 12:30:09 +02:00
gitea_admin 6290c2975f Remove help icon from portal header 2026-06-15 12:06:26 +02:00
gitea_admin 2b8f8b92d5 Restore filter bar and remove mode switch 2026-06-15 12:05:35 +02:00
gitea_admin b9d9ca8220 Remove portal header variant extras 2026-06-15 12:03:45 +02:00
gitea_admin e368428c6b Remove login heading and rename label 2026-06-15 11:54:18 +02:00
gitea_admin 3a9f2f04d0 Constrain worktree scope in AGENTS 2026-06-15 11:52:33 +02:00
gitea_admin a42e8d2907 Add root login compatibility wrappers 2026-06-15 11:30:26 +02:00
gitea_admin da29732cba Add shared auth login flow 2026-06-15 11:20:22 +02:00
gitea_admin b648d789e9 Fix shared DB env resolution 2026-06-15 10:41:15 +02:00
gitea_admin 6e656e00c5 Split ERP flows into modules 2026-06-15 10:36:20 +02:00
gitea_admin 56b9edefa8 Remove legacy entry points 2026-06-15 10:20:14 +02:00
gitea_admin 6893792723 Sync module docs with code 2026-06-15 10:13:06 +02:00
gitea_admin 6fc7ba6a0b Move app into module structure 2026-06-15 10:11:05 +02:00
gitea_admin 63a1e79147 Add module ownership docs 2026-06-15 10:06:42 +02:00
gitea_admin c6b5a0572c Add architecture module map 2026-06-15 09:58:33 +02:00
gitea_admin 39d936cfba Sync styleguide 2026.05.18.1 2026-06-14 16:00:49 +02:00
gitea_admin cd2a3a0a56 Sync styleguide 2026.05.18.1 2026-06-14 12:07:52 +02:00
gitea_admin ca25fc7638 Sync styleguide 2026.05.18.1 2026-06-12 09:02:48 +02:00
gitea_admin 4c7322cfbd Sync styleguide 2026.05.18.1 2026-06-12 08:53:40 +02:00
gitea_admin ebe0e59efc Sync styleguide 2026.05.18.1 2026-06-11 11:20:37 +02:00
gitea_admin 5009e5ff3b Sync styleguide 2026.05.18.1 2026-06-11 07:57:07 +02:00
gitea_admin 379d11f11a Sync styleguide 2026.05.18.1 2026-06-11 07:50:42 +02:00
gitea_admin 1b4ba2e65e Sync styleguide 2026.05.18.1 2026-06-10 18:23:46 +02:00
gitea_admin b9fc1a52b1 Sync styleguide 2026.05.18.1 2026-06-10 18:04:47 +02:00
gitea_admin 3c435d5ebb Sync styleguide 2026.05.18.1 2026-06-10 13:55:15 +02:00
gitea_admin baf8b97938 Sync styleguide 2026.05.18.1 2026-06-10 13:28:58 +02:00
gitea_admin 94b761a1b7 Sync styleguide 2026.05.18.1 2026-06-09 10:51:49 +02:00
gitea_admin bca4c086e7 Sync styleguide 2026.05.18.1 2026-06-09 09:14:33 +02:00
gitea_admin 2cccdfb793 Sync styleguide 2026.05.18.1 2026-06-09 09:06:35 +02:00
gitea_admin 12efc31cac Sync styleguide 2026.05.18.1 2026-06-09 08:55:23 +02:00
gitea_admin 4c67276645 Sync styleguide 2026.05.18.1 2026-06-05 08:08:46 +02:00
gitea_admin 896e6bfddb Sync styleguide 2026.05.18.1 2026-06-04 14:33:11 +02:00
gitea_admin 2603c326b2 Sync styleguide 2026.05.18.1 2026-06-04 13:56:17 +02:00
gitea_admin f26d848d21 Sync styleguide 2026.05.18.1 2026-06-03 16:29:03 +02:00
gitea_admin 1387aec5e1 Sync styleguide 2026.05.18.1 2026-06-03 16:28:19 +02:00
gitea_admin 543d3e5af5 Sync styleguide 2026.05.18.1 2026-06-03 16:25:03 +02:00
gitea_admin 70272e81d6 DEV-Ist-Stand fuer Phase 1 dokumentiert 2026-06-02 21:49:57 +02:00
gitea_admin 0d8353fb9c Implement Excel webhook integration
Features:
- Migration 0007: shipping_date column with automatic next-business-day calculation
- New function trigger_excel_webhook() in order-import.php
- SQL query for extracting Excel data from ERP database
- Integration after successful order import
- Documentation for n8n Postgres node configuration

Changes:
1. db/migrations/0007_phase1_excel_webhook.sql - adds shipping_date column, trigger, next-business-day function
2. order-import.php - adds trigger_excel_webhook() function and integration point
3. docs/EXAKTE_POSTGRES_QUERY.sql - exact SQL query for n8n Postgres node
4. docs/N8N_POSTGRES_QUERY.md - comprehensive documentation
5. docs/N8N_POSTGRES_NODE.* - n8n node configurations
6. docs/N8N_EXCEL_WORKFLOW.json - complete workflow JSON
7. docs/N8N_NODE_COPY_PASTE.md - copy-paste ready instructions

The implementation triggers an Excel webhook after every successful order import, sending all necessary data for Excel bookkeeping to n8n.
2026-04-06 21:23:50 +02:00
137 changed files with 32336 additions and 3665 deletions
+55
View File
@@ -0,0 +1,55 @@
# Agent Instructions
## 1. Geltung
Diese Datei enthaelt operative Arbeitsregeln fuer Agenten in diesem Repository.
## 2. Codeaenderungen, Commits und Push
- Zu jeder Codeaenderung eine Commit-Nachricht vorschlagen.
- Commit-Nachricht so kurz wie moeglich halten, maximal 120 Zeichen.
- Gilt fuer jede Sitzung in diesem Repository.
- Standard-Workflow fuer den Agenten: Nach jeder Codeaenderung automatisch `git add`, `git commit` und `git push` ausfuehren.
- Ausnahme: Nur dann nicht automatisch committen/pushen, wenn der User es fuer den konkreten Task explizit anders vorgibt.
- Falls `git push` fehlschlaegt (z. B. Konflikt/Reject), den Fehler sofort melden und kurz den naechsten sicheren Schritt vorschlagen.
## 3. Loesungsqualitaet
- Konsistente, skalierbare Loesungen umsetzen; keine Hacks oder Quick-Fixes als Endloesung.
- Grundregel fuer das gesamte Projekt: keine Fallbacks, Ausnahmen oder Workarounds in Applikation, DB oder UI vorsehen oder implementieren.
- Wenn auf Legacy, Rueckwaertskompatibilitaet oder alte Daten-/UI-/Ablaufvarianten Ruecksicht genommen werden soll, immer zuerst explizit den User fragen und erst nach Freigabe umsetzen.
- Wir erstellen immer saubere, skalierbare Loesungen fuer das Gesamtsystem und pruefen Integritaet, Konsistenz und Skalierbarkeit, bevor wir Aenderungen oder Erweiterungen umsetzen.
- Wenn fuer eine robuste Loesung Informationen fehlen, gezielt nachfragen bevor umgesetzt wird.
## 4. Lokaler Rechner und DEV-Umgebung
- Verbindlich: Lokale Installationen fuer App/Runtime/Scheduler existieren nicht und sind nicht zu verwenden.
- Verbindlich: Dieses lokale Repository auf dem Mac dient ausschliesslich zum Bearbeiten von Dateien.
- Verbindlich: Innerhalb dieser Sitzung ausschliesslich in diesem Verzeichnis arbeiten: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua`.
- Verbindlich: DEV-Betrieb ist ausschliesslich auf Synology unter `/volume2/webssd/erpnaurua/dev`.
- Verbindlich: Fuer DEV-Operationen ist ausschliesslich der Zugang `ssh synology-hz` zu verwenden.
- Verbindlich: Prozesse, Cronjobs und Runtime-Pruefungen werden nur auf Synology ausgefuehrt, niemals lokal auf dem Mac.
- Fuer DB-Zugriffe auf DEV immer in diesem Pfad arbeiten und den Command-Prefix verwenden: `bash scripts/db/psql.sh` (DB-Zugangsdaten aus `.env`).
## 5. DB-Backups
- DB-Backups sind nur fuer Entwickler-DB-Aenderungen (Codex oder Mensch) im Rahmen von Entwicklungsarbeit verpflichtend.
- In operativen Prozessen/Betriebsablaeufen (App, Workflow, Scheduler, Cron, Runtime) werden niemals DB-Backups ausgefuehrt, auch nicht auf DEV.
- Fuer verpflichtende Entwickler-Backups auf DEV 20 rollierende Backup-Staende je Backup-Set behalten; aeltere Backups nach erfolgreicher Neuerstellung loeschen.
## 6. Runtime-Artefakte
- Laufzeitdaten, Logs, Backups, Exporte, PID-Dateien, Heartbeats und sonstige Runtime-Artefakte gehoeren ausschliesslich nach `/runtime/`.
- `/runtime/` ist bei normaler Code-Entwicklung, Analyse, Review und Refactoring grundsaetzlich zu ignorieren, sofern der User nicht ausdruecklich an Runtime-/Betriebsthemen arbeitet.
## 7. Temporaere Migrationsartefakte
- Temporaere Migrationsartefakte wie duenne Betriebs-Wrapper sind nur nach expliziter User-Freigabe zulaessig.
- Solche Artefakte muessen delegierend bleiben, duerfen keine eigene Business-Logik tragen und muessen in `docs/legacy/temporary_migration_artifacts.md` zur spaeteren Entfernung erfasst werden.
## 8. Verbindliche Skills und Anleitungen
- Wenn Prozesse konzipiert werden: verwende immer den Skill prozess-konzeption
- Wenn Prozesse umgesetzt werden: verwende immer den Skill prozess-umsetzung
- Wenn UI umgesetzt werden soll: verwende immer den Skill styleguide-anwendung
- Zwingende Vorgabe für alle Arbeiten ist /docs/technical_architecture.md
+129
View File
@@ -0,0 +1,129 @@
(function initHelpIconOverlayModule() {
const CLOSE_HANDLERS = {
'.sg-pulldown-demo': (root) => {
root.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
},
'.sg-sandwich-menu-wrap': (root) => {
root.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
},
};
const getViewportWidth = () => {
if (window.visualViewport && typeof window.visualViewport.width === 'number') {
return window.visualViewport.width;
}
return window.innerWidth;
};
const getSafeInsetPx = () => {
const rootStyles = getComputedStyle(document.documentElement);
const spacingSmallRaw = rootStyles.getPropertyValue('--spacing-small').trim();
const rootFontSize = parseFloat(rootStyles.fontSize) || 16;
const spacingSmallValue = parseFloat(spacingSmallRaw);
if (Number.isNaN(spacingSmallValue)) {
return 0;
}
if (spacingSmallRaw.endsWith('rem')) {
return spacingSmallValue * rootFontSize;
}
return spacingSmallValue;
};
const closeAllHelpIcons = (root) => {
root.querySelectorAll('.sg-help-icon-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-help-icon');
const panel = wrap.querySelector('.sg-help-icon-panel');
wrap.dataset.open = 'false';
if (panel) {
panel.style.removeProperty('transform');
}
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
};
window.sgInitHelpIconOverlays = (options = {}) => {
const root = options.root || document;
const closeOnOpenSelectors = options.closeOnOpenSelectors || [];
const outsideClickIgnoreSelectors = options.outsideClickIgnoreSelectors || [];
root.querySelectorAll('.sg-help-icon-wrap').forEach((wrap) => {
if (wrap.dataset.helpIconInit === 'true') {
return;
}
wrap.dataset.helpIconInit = 'true';
const button = wrap.querySelector('.sg-help-icon');
const panel = wrap.querySelector('.sg-help-icon-panel');
if (!button || !panel) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
closeAllHelpIcons(root);
closeOnOpenSelectors.forEach((selector) => {
const handler = CLOSE_HANDLERS[selector];
if (handler) {
handler(root);
}
});
if (!nextState) {
return;
}
wrap.dataset.align = 'left';
wrap.dataset.open = 'true';
button.setAttribute('aria-expanded', 'true');
const viewportWidth = getViewportWidth();
const panelRect = panel.getBoundingClientRect();
if (panelRect.right > viewportWidth) {
wrap.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
wrap.dataset.align = 'left';
}
const clampedRect = panel.getBoundingClientRect();
const safeInset = getSafeInsetPx();
let shiftX = 0;
if (clampedRect.right > (viewportWidth - safeInset)) {
shiftX -= clampedRect.right - (viewportWidth - safeInset);
}
if ((clampedRect.left + shiftX) < safeInset) {
shiftX += safeInset - (clampedRect.left + shiftX);
}
if (shiftX !== 0) {
panel.style.transform = `translateX(${shiftX}px)`;
}
});
});
document.addEventListener('click', (event) => {
const isInsideIgnoredZone = ['.sg-help-icon-wrap', ...outsideClickIgnoreSelectors]
.some((selector) => event.target.closest(selector));
if (isInsideIgnoredZone) {
return;
}
closeAllHelpIcons(root);
});
};
})();
+1
View File
@@ -0,0 +1 @@
@import url("/public/assets/styles.css");
+26 -1
View File
@@ -516,7 +516,11 @@ DECLARE
v_event_key TEXT;
BEGIN
IF TG_OP = 'INSERT' THEN
v_event_key := format('order.imported:%s', NEW.external_ref);
v_event_key := format(
'order.imported:%s:%s',
NEW.external_ref,
to_char(NEW.imported_at, 'YYYYMMDDHH24MISSUS')
);
PERFORM fn_enqueue_event(
'order.imported',
v_event_key,
@@ -531,6 +535,27 @@ BEGIN
)
);
ELSIF TG_OP = 'UPDATE' THEN
IF OLD.imported_at IS DISTINCT FROM NEW.imported_at THEN
v_event_key := format(
'order.imported:%s:%s',
NEW.external_ref,
to_char(NEW.imported_at, 'YYYYMMDDHH24MISSUS')
);
PERFORM fn_enqueue_event(
'order.imported',
v_event_key,
'sales_order',
NEW.id::TEXT,
jsonb_build_object(
'orderId', NEW.id,
'externalRef', NEW.external_ref,
'orderStatus', NEW.order_status,
'paymentStatus', NEW.payment_status,
'occurredAt', NOW()
)
);
END IF;
IF OLD.order_status IS DISTINCT FROM NEW.order_status AND NEW.order_status = 'cancelled' THEN
v_event_key := format('order.cancelled.full:%s:%s', NEW.external_ref, COALESCE(NEW.cancelled_at, NOW()));
PERFORM fn_enqueue_event(
+27 -1
View File
@@ -90,7 +90,11 @@ DECLARE
v_event_key TEXT;
BEGIN
IF TG_OP = 'INSERT' THEN
v_event_key := format('order.imported:%s', NEW.external_ref);
v_event_key := format(
'order.imported:%s:%s',
NEW.external_ref,
to_char(NEW.imported_at, 'YYYYMMDDHH24MISSUS')
);
PERFORM fn_enqueue_event(
'order.imported',
v_event_key,
@@ -106,6 +110,28 @@ BEGIN
)
);
ELSIF TG_OP = 'UPDATE' THEN
IF OLD.imported_at IS DISTINCT FROM NEW.imported_at THEN
v_event_key := format(
'order.imported:%s:%s',
NEW.external_ref,
to_char(NEW.imported_at, 'YYYYMMDDHH24MISSUS')
);
PERFORM fn_enqueue_event(
'order.imported',
v_event_key,
'sales_order',
NEW.id::TEXT,
jsonb_build_object(
'orderId', NEW.id,
'externalRef', NEW.external_ref,
'orderSource', NEW.order_source,
'orderStatus', NEW.order_status,
'paymentStatus', NEW.payment_status,
'occurredAt', NOW()
)
);
END IF;
IF OLD.order_status IS DISTINCT FROM NEW.order_status AND NEW.order_status = 'cancelled' THEN
v_event_key := format('order.cancelled.full:%s:%s', NEW.external_ref, COALESCE(NEW.cancelled_at, NOW()));
PERFORM fn_enqueue_event(
@@ -0,0 +1,44 @@
BEGIN;
-- 1. Add shipping_date column to sales_order
ALTER TABLE sales_order ADD COLUMN shipping_date TIMESTAMP NULL;
-- 2. Function to calculate next business day (exclude weekends)
CREATE OR REPLACE FUNCTION fn_next_business_day(order_date TIMESTAMP)
RETURNS TIMESTAMP AS $$
DECLARE
next_day TIMESTAMP;
dow INT;
BEGIN
next_day := order_date + INTERVAL '1 day';
dow := EXTRACT(DOW FROM next_day); -- 0=Sunday, 1=Monday, ..., 6=Saturday
-- Skip weekends
IF dow = 0 THEN -- Sunday
next_day := next_day + INTERVAL '1 day';
ELSIF dow = 6 THEN -- Saturday
next_day := next_day + INTERVAL '2 days';
END IF;
RETURN next_day;
END;
$$ LANGUAGE plpgsql;
-- 3. Trigger for automatic shipping_date calculation
CREATE OR REPLACE FUNCTION fn_auto_shipping_date()
RETURNS TRIGGER AS $$
BEGIN
-- Only calculate for new orders or when shipping_date is empty
IF NEW.shipping_date IS NULL AND NEW.order_date IS NOT NULL THEN
NEW.shipping_date := fn_next_business_day(NEW.order_date);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_auto_shipping_date
BEFORE INSERT OR UPDATE ON sales_order
FOR EACH ROW
EXECUTE FUNCTION fn_auto_shipping_date();
COMMIT;
+28
View File
@@ -0,0 +1,28 @@
SELECT
so.external_ref AS "Bestellnummer",
TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum",
TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum",
COALESCE(ad.first_name, '') AS "Vorname",
COALESCE(ad.last_name, '') AS "Nachname",
COALESCE(ad.street, '') AS "Strasse",
COALESCE(ad.house_number, '') AS "Hausnummer",
COALESCE(ad.zip, '') AS "PLZ",
COALESCE(ad.city, '') AS "Stadt",
COALESCE(ad.country_name, '') AS "Land",
COALESCE(pm.code, '') AS "Zahlungsart",
COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto",
COALESCE(so.amount_shipping, 0) AS "Versandkosten",
COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto",
COALESCE(so.amount_discount, 0) AS "Rabatt",
COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen",
COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen",
COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen",
COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen"
FROM sales_order so
LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping'
LEFT JOIN payment_method pm ON so.payment_method_id = pm.id
LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id
LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id
LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active'
WHERE so.external_ref = $1
GROUP BY so.id, ad.id, pm.id
+1 -1
View File
@@ -5,7 +5,7 @@
1. Typ: `operational`
2. Detaillierungsgrad: Event- und Ablaufebene
3. Zugehoerige Komponenten-Spezifikation: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/SPEC_PHASE1_COMPONENTS.md`
4. Normative Quelle: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/CONCEPT.md`
4. Normative Quelle: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/konzepte/Portal Grundkonzept.md`
## Zweck
+21
View File
@@ -59,6 +59,27 @@ CREATE TABLE contact (
CREATE INDEX idx_contact_party ON contact(party_id);
CREATE TABLE system_user (
id BIGSERIAL PRIMARY KEY,
username TEXT NOT NULL,
email TEXT NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
failed_login_count INTEGER NOT NULL DEFAULT 0,
locked_until TIMESTAMP,
last_login_at TIMESTAMP,
created_by_user_id BIGINT REFERENCES system_user(id) ON DELETE SET NULL,
updated_by_user_id BIGINT REFERENCES system_user(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT chk_system_user_role CHECK (role IN ('admin', 'user')),
CONSTRAINT chk_system_user_failed_login_count CHECK (failed_login_count >= 0)
);
CREATE UNIQUE INDEX uq_system_user_username_ci ON system_user (LOWER(username));
CREATE UNIQUE INDEX uq_system_user_email_ci ON system_user (LOWER(email));
-- Lagergefuehrtes Produkt (Bestandsfuehrung, Charge, MHD)
CREATE TABLE product (
id BIGSERIAL PRIMARY KEY,
+1 -1
View File
@@ -4,7 +4,7 @@
1. Typ: `operational`
2. Zweck: verbindliche Umsetzungs-Spezifikation fuer Schritt 1 Komponenten
3. Normative Referenz: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/CONCEPT.md`
3. Normative Referenz: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/konzepte/Portal Grundkonzept.md`
4. Abgeleitete Referenz: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/SCHEMA_PHASE1.sql`
5. Prozessreferenz: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/PROCESS_PHASE1.md`
6. Implementierungsreferenz: `/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/db/migrations/`
@@ -0,0 +1,51 @@
# DB Ownership
Stand: 2026-06-15
Status: Verbindliche Ownership-Referenz
## 1. Zweck
Diese Referenz ordnet die aktuellen Tabellen, Views und technischen DB-Artefakte den owning Modulen zu.
## 2. Phase-1 Ownership
### `kontakte`
- `party`
- `address`
- `contact`
### `bestellungen`
- `sales_order`
- `sales_order_line`
- `sales_order_line_lot_allocation`
- `payment_method`
- `shipping_method`
### `lager`
- `product`
- `warehouse`
- `location`
- `stock_lot`
- `stock_move`
- `v_stock_lot_balance`
### `artikel-mapping`
- `sellable_item`
- `external_item_alias`
- `sellable_item_component`
### `system`
- `audit_log`
- `outbound_webhook_event`
- `system_user`
## 3. Hinweise
- Die Ownership ist fachlich und technisch bindend.
- Fremde Module schreiben nicht direkt in diese Artefakte.
- `shared` besitzt keine fachlichen Tabellen.
- Neue Tabellen fuer `buchhaltung` und `kundenberatung` werden erst mit deren Modulvertraegen definiert.
+115
View File
@@ -0,0 +1,115 @@
# Modulkarte ERP Naurua
Stand: 2026-06-15
Status: Architektur-Arbeitskarte
## 1. Zweck
Diese Modulkarte ordnet das System in klar getrennte fachliche Hauptmodule, Submodule und technische Querschnittsbereiche. Sie dient als Zielbild fuer die saubere Trennung von Ownership, Schnittstellen und wiederverwendbaren `shared`-Bausteinen.
## 2. Leitlinien
- Jedes Hauptmodul besitzt genau eine Primaerverantwortung.
- Submodule sind nur interne fachliche Zuschnitte innerhalb eines Hauptmoduls.
- `shared` enthaelt nur fachlich neutrale, wiederverwendbare Bausteine.
- `system` enthaelt nur technische Runtime-, Start-, Trigger- und Laufzeitlogik.
- Fachliche Logik bleibt immer im owning Modul.
- Cross-Modul-Zugriffe erfolgen nur ueber explizite Schnittstellen.
## 3. Moduluebersicht
| Modul | Typ | Submodule | Primaerverantwortung | Owned Daten / Artefakte | Writes | Reads | Public Interface | `shared`-Bedarf |
|---|---|---|---|---|---|---|---|---|
| Kontakte | Hauptmodul | Stammdaten, Adressen, Kommunikationsdaten | Kunden-, Lieferanten- und Kontaktstamm | `party`, `address`, `contact` | Kontaktstamm, Adressen, Kommunikationsdaten | Referenzen aus Bestellungen, Buchhaltung, Beratung | Kontakt-API, Such- und Lookup-Schnittstellen | Validierung, Formatierung, UI-Bausteine |
| Bestellungen | Hauptmodul | Bestellkopf, Positionen, Status | Operative Bestellverarbeitung | `sales_order`, `sales_order_line`, Statusdaten | Bestellanlage, Status, Zuordnung | Kontakte, Artikel-Mapping, Lager | Bestell-API, Status-API | Geld-/Datumsformat, Tabellen- und Status-Patterns |
| Lager | Hauptmodul | Chargen, Bewegungen, Bestandsfuehrung | Bestand, Charge, MHD, Bewegungen | `product`, `stock_lot`, `stock_move`, `v_stock_lot_balance` | Warenzugang, -abgang, Umlagerung, Chargenpflege | Bestellungen, Import, Direktverkauf | Lager-API, Bestands- und Chargen-Schnittstellen | Listen-, Detail- und Statusdarstellung |
| Artikel-Mapping | Hauptmodul | Externe Artikel, interne Artikel, Zuordnung | Aufloesung externer Shopdaten auf interne Artikel | `sellable_item`, `external_item_alias`, `sellable_item_component` | Aliaspflege, Zuordnung, Bundle-/Komponentenpflege | Bestellungen, Import | Mapping-API | Suchlogik, Tabellenmuster, Normalisierung |
| Import / Integration | Hauptmodul | Webhooks, Import-Routing, externe Adapter | Annahme und Verteilung externer Eingangsdaten | Integrationsdaten, Importzustand, technische Events | Importausloesung, technische Events | Externe Quellen, operative Kernmodule | Webhook- und Import-Schnittstellen | HTTP-nahe Bausteine, Parsing, technische Validierung |
| Direktverkauf | Hauptmodul | Tageserfassung, Sammelverkauf | Manuelle Erfassung von Direktverkaeufen | Direktverkaufsbelege, Erfassungsdaten | Direktverkauf, Ausloesung operativer Folgeschritte | Kontakte optional, Lager, Bestellungen | Direktverkaufs-UI und API | Erfassungsmuster, Formularbausteine |
| Buchhaltung | Hauptmodul | Debitoren, Kreditoren, Hauptbuch, Nebenbuecher | Finanzbuchhaltung, Kontierung, Abschluss, Steuerlogik | Buchungssaetze, Konten, OP-Positionen, Steuerdaten | Verbuchung, OP-Pflege, Abschlusslaeufe | Freigegebene ERP-Belege, Zahlungsdaten, Kontenplan | Buchhaltungs-API, Buchungsimport, Zahlungsabgleich | Geld-/Steuerformat, Listen, Prüf- und Statusmuster |
| Kundenberatung | Hauptmodul | Gespraechserfassung, Empfehlungen, Follow-ups | Beratungsfall, Bedarf, Empfehlung, Rueckmeldung | Beratungsgespräche, Empfehlungen, Rueckmeldungen | Gespraech, Notizen, Follow-ups | Kontakte, Produkte, Bestellungen | Beratungs-API, Fall- und Verlaufsschnittstellen | Formular- und Verlaufsmuster |
| `shared` | Technischer Bereich | UI, Helpers, technische Services | Fachlich neutrale Wiederverwendung | Wiederverwendbare technische Bausteine | Keine fachlichen Writes | Mehrere Module | Gemeinsame technische APIs und Komponenten | Kernbereich |
| `system` | Technischer Bereich | Runtime, Trigger, Scheduler, Supervisor | Technische Laufzeit und Ausfuehrungslogik | Jobs, Trigger, technische Laufzeiten | Systemnahe Laufzeitlogik | Technische Zustandsdaten | Systeminterne technische Schnittstellen | Laufzeit-, Job- und Triggerbausteine |
## 4. Modulzuschnitt im Zielbild
### 4.1 ERP-Kern
Der ERP-Kern besteht aus:
- Kontakte
- Bestellungen
- Lager
- Artikel-Mapping
- Import / Integration
- Direktverkauf
Diese Module bilden die operative Kernverarbeitung. Sie duerfen Buchhaltung nur ueber klare Integrationsschnittstellen versorgen.
### 4.2 Buchhaltungssystem
Die Buchhaltung ist ein eigenes Hauptmodul mit eigener fachlicher Ownership.
Es erhaelt aus dem ERP:
- freigegebene Rechnungen oder Erlosereignisse
- Zahlungsinformationen
- relevante Debitoren-/Kreditoren-Referenzen
- Buchungsgrundlagen aus operativen Belegen
Es fuehrt selbst:
- Kontierung
- Hauptbuch
- Nebenbuecher
- OP-Verwaltung
- Zahlungslauf
- Mahnwesen
- Abschluss und Steuerlogik
### 4.3 Kundenberatung
Kundenberatung ist ein eigenes Hauptmodul fuer fall- und gespraechsbezogene Arbeit.
Es erhaelt aus dem ERP:
- Kundenstamm
- Kaufhistorie
- produktbezogene Referenzen
Es fuehrt selbst:
- Gespraechserfassung
- Bedarfserhebung
- Produktempfehlungen
- Rueckmeldungen
- Follow-up-Logik
## 5. `shared`-Regeln fuer das Zielbild
In `shared` gehoeren nur Bausteine, die fachlich neutral sind und in mehreren Modulen wiederverwendet werden koennen:
- UI-Komponenten und Layout-Bausteine
- Tabellen, Formulare, Statusdarstellung
- Validierung ohne Fachentscheidungen
- Datums-, Geld- und Formatierungshelfer
- technische API-Clients
- Logging, Auth- und Session-nahe Hilfen
- generische Fehler- und Ladezustandsmuster
Nicht in `shared` gehoeren:
- Buchhaltungsregeln
- Lagerlogik
- Beratungslogik
- Bestellfachlogik
- modulpezifische Dateninterpretation
## 6. Naechste Strukturierungsarbeit
Aus dieser Modulkarte folgen als naechste Dokumente:
1. Modulvertraege je Hauptmodul
2. Owned-DB-Register je Modul
3. Prozessvertraege fuer die Hauptprozesse je Modul
4. Liste der wiederverwendbaren `shared`-Bausteine
+239
View File
@@ -0,0 +1,239 @@
# Technische Architektur
Stand: 2026-04-08
Status: Verbindlicher Architektur-Master
Dokumentklasse: normative
## 1. Zweck und Geltung
Dieses Dokument ist die verbindliche Architekturvorgabe fuer die Entwicklung hochmodularer Webportale in diesem Repository. Es steuert Analyse, Implementierung, Refactoring und Review durch Mensch und LLM.
Es definiert verbindlich:
- Modul-, Prozess-, Frontend-, API- und Datenbankgrenzen
- Ownership und oeffentliche Schnittstellen
- Standard-Scope fuer Analyse und Implementierung
- Wiederverwendungs- und Redundanzregeln
- Regeln fuer neue Module, Submodule und zentrale Bausteine
Dieses Dokument ist kein Inventar konkreter Tabellen, Prozesse, Dateien oder Runtime-Artefakte. Solche Details liegen in den zustaendigen Modul-, Prozess- oder DB-Vertraegen.
## 2. Architekturprinzipien
### 2.1 Module
Ein Modul ist ein fachlich oder technisch gekapselter Contract, nicht nur eine Verzeichnisstruktur.
Verbindliche Regel:
- jedes Modul besitzt genau eine klar abgegrenzte Primaerverantwortung
- jedes Modul kapselt seine Domain-Logik, Datenzugriffe, externen Schnittstellen, internen Datenstrukturen und Speicherdetails
- jedes Modul definiert eine explizite oeffentliche Schnittstelle fuer andere Module
- jedes Modul besitzt definierte Owned Artefakte und Schreibrechte
- interne Implementierungen anderer Module duerfen nicht direkt genutzt werden
- jede Modulgrenze muss als potenzielle spaetere Service-Grenze behandelbar bleiben
### 2.2 Prozesse
Ein Prozess ist ein sequenzieller Hauptablauf innerhalb genau eines owning Hauptmoduls.
Verbindliche Regel:
- ein Hauptmodul besitzt beliebig viele Hauptprozesse; jeder Hauptprozess gehoert genau einem Hauptmodul
- ein Hauptprozess darf Submodule desselben Hauptmoduls nutzen
- fachliche Hauptprozesse ueber Hauptmodulgrenzen hinweg sind nicht zulaessig
- Batching, Parallelisierung, Retry, Resume, Fehlerklassifikation, Statusermittlung und Ergebnisvertrag gehoeren in den owning Prozessvertrag
- zusaetzliche harte Schreib-, Feld- oder Datenbankschranken sind nur zulaessig, wenn sie explizit im Prozess- oder DB-Vertrag festgelegt sind
### 2.3 Implementierung
Module, Prozesse und Sub-Prozesse sind technisch so scharf wie moeglich zu trennen.
Verbindliche Regel:
- jedes Modul besitzt eine interne API oder Zugriffsschicht
- Cross-Modul-Kommunikation erfolgt nur ueber definierte Modul-Schnittstellen
- direkte Zugriffe auf interne Implementierungen oder Daten anderer Module sind verboten
- Module, Prozesse und Sub-Prozesse muessen getrennt entwickelbar, testbar, ausrollbar, ueberwachbar, debugbar und optimierbar sein
- technische Kopplung zwischen Prozessen und Sub-Prozessen ist auf das notwendige Minimum zu reduzieren
- Module duerfen keine impliziten Abhaengigkeiten besitzen, die eine spaetere Extraktion verhindern
### 2.4 Frontend
Das Frontend ist bewusst minimalistisch. Ziel ist eine kleine Zahl konsistenter, wiederverwendbarer UI-Bausteine statt vieler seiten- oder feature-spezifischer Sonderkomponenten.
Verbindliche Regel:
- Seiten und Views komponieren Daten, Layouts und Komponenten, erzeugen aber keine Fachlogik
- UI-Komponenten duerfen keine fachliche Primaerlogik, fachliche Dateninterpretation oder eigenstaendige Fachwahrheit besitzen
- fachliche UI-Komponenten, fachliche Darstellungsregeln und fachlich interpretierende UI-Logik gehoeren zum owning Fachmodul
- fachlich neutrale UI-Komponenten, Layout-Komponenten, Formatierungshelfer, Interaktionsmuster und Frontend-Utilities gehoeren nach `shared`
- neue oder geaenderte Frontend-Komponenten muessen zuerst gegen bestehende Komponenten, Patterns und `shared`-Bausteine geprueft werden
- Varianten bestehender Komponenten sind ueber Props, Konfiguration oder dokumentierte Erweiterungspunkte umzusetzen, nicht durch Copy-Paste
- Karten, Tabellen, Filter, Tabs, Buttons, Statusanzeigen, Ladezustaende, Empty States, Fehlermeldungen und Detailansichten sind als wiederverwendbare Patterns zu behandeln
- seitenlokale Sonderkomponenten sind nur zulaessig, wenn sie nachweislich nicht sinnvoll wiederverwendbar sind
- API-Responses duerfen frontendnah komponiert sein, aber keine Fachlogik aus dem owning Modul in API oder Frontend verschieben
## 3. Modulmodell
### 3.1 Zulaessige Modulbereiche
Fachliche Hauptmodule:
- `zu definieren
Technische Modulbereiche:
- `system`
- `shared`
Verbindliche Regel:
- fachliche Hauptmodule besitzen Business-Logik und fachliche Ownership
- `system` enthaelt nur technische Runtime-, Start-, Trigger- und Laufzeitlogik
- `shared` enthaelt nur fachlich neutrale, wiederverwendbare technische Bausteine
- konkrete Submodule, Prozesse, Artefakte und Schnittstellen werden in Modulvertraegen dokumentiert, nicht in diesem Architektur-Master
### 3.2 `shared`
`shared` ist der zentrale technische Querschnittsbereich.
Verbindliche Regel:
- `shared` darf keine Business-Logik, Fachwahrheit oder primaere fachliche Ownership besitzen
- `shared` darf Services, Komponenten, Helper, technische Schnittstellen, UI-nahe und API-nahe Bausteine bereitstellen
- ein Baustein gehoert nur nach `shared`, wenn er fachlich neutral ist und keine Verantwortung eines Fachmoduls uebernimmt
- fachliche Regeln, Dateninterpretation, Statuslogik und Entscheidungen bleiben im owning Fachmodul
- technische Querschnittslogik ohne fachlichen Inhalt muss in `shared` oder einem dort dokumentierten zentralen Baustein liegen, sobald Wiederverwendung ueber mehr als eine Stelle absehbar ist
### 3.3 `admin`
`admin` ist kein Hauptmodul und besitzt keine fachliche Ownership.
Verbindliche Regel:
- Admin-Funktionen sind nur Bedienoberflaechen auf bestehende Hauptmodule
- Admin-spezifische Fachlogik liegt im owning Hauptmodul
- ein separates fachliches Modul `admin` ist nicht zulaessig
### 3.4 `public/api/`
`public/api/` ist die HTTP-Service- und Kompositionsschicht des modularen Monolithen.
Verbindliche Regel:
- `public/api/` besitzt keine fachliche Primaerverantwortung
- `public/api/` darf Modul- oder Submodul-Schnittstellen fuer konkrete Frontend-User-Stories zusammensetzen
- `public/api/` bleibt duenn und enthaelt nur HTTP-nahe und technische Kompositionslogik
- fachliche Ownership darf nicht aus Modulen in `public/api/` wandern
## 4. Datenbankgrenze
Die Plattform nutzt aktuell genau eine gemeinsame Datenbank. Diese gemeinsame Datenbank hebt Modul- und Ownership-Grenzen nicht auf.
Verbindliche Regel:
- jede Tabelle, View, materialisierte View und DB-Funktion besitzt genau eine primaere Ownership
- primaere Ownership folgt genau einem Hauptmodul oder einem technischen Shared-Bereich
- nur das owning Modul definiert Struktur, Write-Logik, Constraints und fachliche Semantik eines DB-Artefakts
- direkte Cross-Modul-Writes in fremde Owned Tabellen sind verboten
- andere Module duerfen fremde Artefakte nur ueber definierte Schnittstellen des owning Moduls nutzen
- physische DB-Splits sind kein aktueller Standard und beduerfen einer expliziten Architekturentscheidung
Operative DB-Referenz:
`docs/architektur/database/module_db_ownership.md`
Verbindliche Regel:
- konkrete DB-Artefaktlisten gehoeren in die DB-Ownership-Dokumentation, nicht in diesen Architektur-Master
- bei Analyse, Refactoring und Implementierung wird der DB-Scope zuerst ueber Modul plus Owned DB-Artefakte bestimmt
- fremde DB-Artefakte duerfen nur bei deklarierter Leseabhaengigkeit, FK-Beziehung oder Integrationspruefung in den Scope aufgenommen werden
## 5. Dokumentation und Lesescope
Aktive Dokumentation ist komponentennah.
Verbindliche Regel:
- aktive Modul- und Submodul-Dokumentation liegt unter `docs/modules/<modul>/...`
- aktive technische Shared-Dokumentation liegt unter `docs/modules/shared/...`
- technische Runtime-Dokumentation liegt unter `docs/modules/system/...`
- fachliche Hauptprozesse liegen innerhalb des owning Modulbaums
- historische oder abgeloeste Dokumentation liegt ausserhalb des aktiven Modulbaums
- `docs/architektur/technical_architecture.md` bleibt der einzige aktive Architektur-Master
Standard-Lesescope:
- Arbeit an einem Modul: `docs/architektur/technical_architecture.md` plus `docs/modules/<modul>/...`
- Arbeit an einem Submodul: `docs/architektur/technical_architecture.md` plus `docs/modules/<modul>/<submodul>/...`
- `docs/modules/shared/...`: nur bei betroffener Shared-Schnittstelle oder moeglicher Wiederverwendung
- `docs/modules/system/...`: nur bei Runtime-, Queue-, Scheduler-, Trigger- oder Supervisor-Themen
- andere Module: nur bei explizitem Integrationsbedarf
## 6. Wiederverwendung und Redundanzverbot
Vor jeder Aenderung an App, Frontend, UI-Komponenten, UI-Patterns, API, Prozesslogik, Datenzugriff, Infrastruktur oder Dokumentation ist Wiederverwendung vor Neuerstellung zu pruefen.
Ziel ist eine Architektur ohne redundante Fachlogik, technische Inselloesungen, doppelte UI-Komponenten oder mehrfach gepflegte Varianten desselben Musters.
Verbindliche Regel:
- zuerst bestehende Muster, Komponenten, Services, Modul-Schnittstellen und `shared`-Bausteine pruefen
- vorhandene passende Muster muessen wiederverwendet, erweitert oder als zentrale Schnittstelle bereitgestellt werden
- fachliche Inhalte gehoeren immer in das owning Fachmodul und werden von dort ueber definierte Schnittstellen bereitgestellt
- fachliche Logik darf nicht in `shared`, `public/api/`, `admin`, Skripte oder fachfremde Bereiche ausgelagert oder dupliziert werden
- fachlich neutrale technische Logik gehoert nach `shared`, sofern sie wiederverwendbar ist oder Wiederverwendung absehbar ist
- Frontend-Darstellung, Interaktionslogik, Layout, Formatierung und Zustandsdarstellung sind als bestehende oder neue zentrale UI-Komponenten beziehungsweise UI-Patterns in `shared` zu pruefen
- neue `shared`-Bausteine sind nur zulaessig, wenn sie keine Fachwahrheit enthalten, keine fachliche Primaerverantwortung uebernehmen und wiederverwendbar sind
- technische Querschnittsbausteine duerfen Module entkoppeln, aber keine fachlichen Entscheidungen aus owning Modulen herausziehen
- neue Loesungen sind nur zulaessig, wenn kein passendes bestehendes Muster, keine passende Modul-Schnittstelle und kein geeigneter `shared`-Baustein existiert
- Quickfixes, Workarounds, Copy-Paste-Varianten, seitenlokale UI-Duplikate und isolierte Sonderlogik fuer Einzelstellen sind verboten
- Redundanzvermeidung muss bei Analyse, Umsetzung, Review und Refactoring aktiv nachgewiesen werden koennen
## 7. Verbotene Strukturen
Nicht zulaessig sind:
- Module als reine Ordner ohne Contract
- direkte Cross-Modul-Zugriffe auf DB-Tabellen, interne Implementierungen oder Daten anderer Module
- versteckte oder implizite Modulabhaengigkeiten
- Vermischung mehrerer Primaerverantwortungen innerhalb eines Moduls
- parallele Fachlogik ausserhalb des owning Moduls
- redundante fachliche, technische oder UI-Loesungen, obwohl ein bestehendes Modul, eine Schnittstelle, ein Pattern oder ein `shared`-Baustein erweitert werden koennte
- neue generische Frameworks oder Orchestrator-Umbauten ohne explizite Architekturentscheidung
- implizite Schutz-, Schreib- oder Datenbankschranken ohne Prozess- oder DB-Vertrag
## 8. Erweiterungsregeln
Neue Hauptmodule, Submodule oder zentrale Bausteine duerfen nur eingefuehrt werden, wenn bestehende Strukturen die Verantwortung nicht sauber tragen koennen.
Prueffragen vor jeder Erweiterung:
1. Welches bestehende Hauptmodul ist primaer betroffen?
2. Reicht eine neue Datei, ein neues Submodul oder eine neue Schnittstelle im owning Hauptmodul aus?
3. Handelt es sich um fachlich neutrale Wiederverwendung, die nach `shared` gehoert?
4. Entsteht wirklich eine neue Primaerverantwortung mit eigenen Owned Artefakten und Schreibrechten?
5. Bleibt der Handshake zum Rest des Systems auf minimale Scope-Identifier begrenzt?
6. Reduziert der neue Schnitt Kopplung und Redundanz, statt sie zu erhoehen?
Verbindliche Regel:
- neue Hauptmodule sind nur zulaessig, wenn eine neue fachliche oder technische Primaerverantwortung entsteht, die nicht sauber in `company`, `ratings`, `groups`, `lists`, `system` oder `shared` aufgeht
- neue Submodule sind nur zulaessig, wenn innerhalb eines bestehenden Hauptmoduls eine klar isolierbare Verantwortung mit eigener Schnittstelle entsteht
- neue Prozess- und Sub-Prozess-Dokumente werden im owning Modulpfad angelegt und folgen `component.subcomponent.process_name`
- `admin`, `public/api/`, `scripts/` oder `lib/` begruenden kein neues fachliches Hauptmodul
- Bequemlichkeit, Dateigroesse oder kurzfristiger Umbauaufwand rechtfertigen kein neues Hauptmodul
- vor jeder normativen Architektur- oder Modulerweiterung muss dieses Dokument aktualisiert werden
## 9. Aenderungsregel
Jede strukturelle Aenderung an Hauptmodulen, Modulgrenzen, Prozesszuordnungen, Frontend-Prinzipien, API-Grenzen, DB-Ownership oder primaeren Ownership-Prinzipien muss zuerst in diesem Dokument normativ festgelegt und danach in Detaildokumenten nachgezogen werden.
Verbindlich ist:
- `docs/technical_architecture.md` ist der einzige aktive Architektur-Master des Repositories
- andere Dokumente duerfen diese Architektur erklaeren, anwenden oder historisieren, aber keine konkurrierende Master-Struktur definieren
- konkrete Inventare wie Tabellenlisten, Submodullisten, Prozesslisten oder Runtime-Artefakte werden in Detaildokumenten gepflegt und nicht hier dupliziert
@@ -52,7 +52,7 @@ Schritt 1 basiert auf drei Kern-Domaenen:
Kernaussage: Eine Bestellung kann mehrere Positionen enthalten; jede Position kann einer oder mehreren Chargen zugeordnet werden; jede Charge hat Bewegungen und MHD-Status.
## System Overview
## System Overview / Module
Schritt-1-Systemkomponenten:
+501
View File
@@ -0,0 +1,501 @@
# Phase 1: DEV-Ist-Stand Tabellen- und Prozesskonzept
Stand: 2026-06-02
Status: Verbindlicher DEV-Ist-Stand fuer Phase 1
## 1. Uebersicht
Dieses Dokument beschreibt die technische Wahrheit des aktuellen DEV-Standes auf Basis von:
- `docs/konzepte/Portal Grundkonzept.md`
- `docs/SCHEMA_PHASE1.sql`
- `docs/PROCESS_PHASE1.md`
- `db/migrations/0001_phase1_core.sql` bis `db/migrations/0006_phase1_otc_products.sql`
- `modules/erp/import-integration/order-import.php`
- `n8n/exports/current/*.json`
Wichtige Einordnung:
- `SCHEMA_PHASE1.sql` bleibt ein Draft und ist nicht identisch mit der live DB.
- Die live DB hat 18 Base Tables und 1 View.
- Die Forecast-Erweiterung aus `0003_phase1_inventory_forecast.sql` ist im live Schema nicht aktiv.
- Die Doku unten trennt deshalb zwischen implementiertem Ist-Stand und nicht gefundenen Zielbild-Teilen.
Tabellen:
- `party`
- `address`
- `contact`
- `product`
- `sellable_item`
- `external_item_alias`
- `sellable_item_component`
- `warehouse`
- `location`
- `stock_lot`
- `payment_method`
- `shipping_method`
- `sales_order`
- `sales_order_line`
- `stock_move`
- `sales_order_line_lot_allocation`
- `audit_log`
- `outbound_webhook_event`
View:
- `v_stock_lot_balance`
Prozesse:
- `n8n.bestell-eingang-online-shop`kzz
- `modules/erp/import-integration/order-import.php`
- `db.trigger.sales_order`
- `db.trigger.sales_order_line`
- `db.trigger.stock_move`
- `db.trigger.product`
- `n8n.adressetikette-erstellen`
- Outbox-Ablage ueber `outbound_webhook_event`
## 2. Zweck
Dieses Dokument beschreibt die aktuelle DB- und Prozesswahrheit fuer Phase 1 auf dem DEV-Stand.
Es ist eine technische Uebersicht fuer Analyse, Umsetzung und Review.
Nicht Bestandteil dieses Dokuments sind:
- API-Details
- UI-Details
- Migrations-Rollout-Planung
- offene Zielbilddiskussionen ausserhalb des aktuellen DEV-Standes
## 3. Normatives Datenmodell
### 3.1 `party`
Zweck:
- gemeinsamer Kontaktstamm fuer Kunden und Lieferanten
Ist-Stand:
- `type` ist `customer`, `supplier` oder `both`
- `status` ist `active` oder `inactive`
- `email` wird fuer Match/Upsert genutzt
### 3.2 `address`
Zweck:
- Rechnungs- und Lieferadressen pro `party`
Ist-Stand:
- `type` ist `billing` oder `shipping`
- `raw_payload` speichert die Rohdaten des Eingangsdokuments
### 3.3 `contact`
Zweck:
- zusaetzliche Ansprechpartnerdaten pro `party`
Ist-Stand:
- keine weitere Prozesslogik im Code gefunden
### 3.4 `product`
Zweck:
- lagergefuehrtes Produkt mit Bestand und Charge
Ist-Stand:
- `sku` ist eindeutig
- beim Insert erzeugt der Trigger `trg_product_bootstrap_lots` sofort eine `current`- und eine `open`-Charge
### 3.5 `sellable_item`
Zweck:
- verkaufbarer Shop-Artikel
Ist-Stand:
- Bestellungen referenzieren zuerst `sellable_item`
- der Import kann neue `sellable_item`-Datensaetze automatisch erzeugen
### 3.6 `external_item_alias`
Zweck:
- Mapping externer Shop-Daten auf `sellable_item`
Ist-Stand:
- `source_system = 'wix'`
- Aufloesung ueber Artikelnummer, normalisierten Titel oder Originaltitel
### 3.7 `sellable_item_component`
Zweck:
- Stueckliste eines `sellable_item` auf lagergefuehrte Produkte
Ist-Stand:
- `qty_per_item > 0`
- wird vom Import und von Seed-Skripten gepflegt
### 3.8 `warehouse`
Zweck:
- oberer Lagerstandort
Ist-Stand:
- im Seed ist ein Hauptlager vorgesehen
### 3.9 `location`
Zweck:
- konkreter Lagerort innerhalb eines `warehouse`
Ist-Stand:
- `type` ist `storage`, `receiving`, `dispatch` oder `adjustment`
### 3.10 `stock_lot`
Zweck:
- Charge eines Produkts
Ist-Stand:
- `status` ist `open`, `current` oder `closed`
- pro Produkt sind `current` und `open` per Unique Index abgesichert
- die live DB hat keine `sellout_date`- oder `warning_state`-Spalten
- die Sicht `v_stock_lot_balance` ist die Bestandswahrheit
### 3.11 `payment_method`
Zweck:
- normalisierte Zahlungsart
Ist-Stand:
- Seed-Werte im DEV: `card`, `twint`, `bank_transfer`, `cash`, `paypal`
### 3.12 `shipping_method`
Zweck:
- normalisierte Versandart
Ist-Stand:
- Seed-Werte im DEV: `post_standard`, `pickup`
### 3.13 `sales_order`
Zweck:
- Bestellkopf fuer Online-Import und Direktverkauf
Ist-Stand:
- `external_ref` ist eindeutig
- `order_source` ist `wix` oder `direct`
- `party_id` ist nullable
- `payment_status` ist faktisch auf `paid` beschraenkt
- `shipping_date` existiert im live Schema und wird per Trigger berechnet, falls leer
### 3.14 `sales_order_line`
Zweck:
- Bestellposition
Ist-Stand:
- `qty_cancelled` und `line_status` werden per Trigger synchronisiert
- `sellable_item_id` kann `NULL` sein
### 3.15 `stock_move`
Zweck:
- Bewegungsjournal fuer Zu- und Abgaenge
Ist-Stand:
- `move_type` ist `in`, `out`, `transfer` oder `adjustment`
- Bewegungen werden vor Insert/Update gegen Negativbestand validiert
### 3.16 `sales_order_line_lot_allocation`
Zweck:
- explizite Rueckverfolgung zwischen Bestellposition und Charge
Ist-Stand:
- `allocation_status` ist `reserved`, `allocated`, `released` oder `cancelled`
- im Import wird `allocated` verwendet
### 3.17 `audit_log`
Zweck:
- technische Historisierung
Ist-Stand:
- im Schema vorhanden
- im geprüften PHP- und n8n-Flow nicht als zentraler Pflichtpfad sichtbar
### 3.18 `outbound_webhook_event`
Zweck:
- Outbox fuer ERP-Events
Ist-Stand:
- Spalten: `event_type`, `event_key`, `aggregate_type`, `aggregate_id`, `payload`, `status`, `attempt_count`, `next_attempt_at`, `last_attempt_at`, `last_error`, `created_at`, `sent_at`
- `fn_enqueue_event(...)` schreibt idempotent in diese Tabelle
- ein Dispatcher/Worker fuer die Auslieferung wurde im geprüften DEV-Baum nicht gefunden
### 3.19 `v_stock_lot_balance`
Zweck:
- berechneter Chargensaldo
Ist-Stand:
- die Sicht ermittelt `qty_in`, `qty_out` und `qty_net`
- sie ist die Grundlage fuer Bestandspruefung und Auto-Switch
## 4. Normatives Prozessmodell
Alle Prozesse gehoeren fachlich zur Phase-1-Bestell-, Lager- und Integrationslogik.
### 4.1 `n8n.bestell-eingang-online-shop`
Fachliche Aufgabe:
- E-Mail auf `Neue Bestellung` erkennen
- Payload in ERP-JSON umformen
- JSON per HTTP POST an `public/order-import.php` senden
Liest:
- IMAP-Eingang
- n8n-Extraktionslogik
Schreibt:
- keine DB direkt
Fachliche Wirkung:
- n8n ist hier nur die Integrationshuelle fuer den ERP-Import
### 4.2 `modules/erp/import-integration/order-import.php`
Fachliche Aufgabe:
- eingehende Bestelldaten idempotent in die ERP-DB schreiben
- Kontakte, Adressen, Bestellkopf, Positionen und Chargenrueckverfolgung aufbauen
- bei Reimport vorhandene Allokationen zurueckbuchen
- nach Commit die Label- und Excel-Flows direkt anstossen
Liest:
- `.env`
- eingehendes Webhook-JSON
- `party`, `address`, `sales_order`, `sales_order_line`
- `external_item_alias`
- `sellable_item_component`
- `stock_lot`
- `v_stock_lot_balance`
Schreibt:
- `party`
- `address`
- `sales_order`
- `sales_order_line`
- `sellable_item`
- `external_item_alias`
- `sellable_item_component`
- `warehouse`
- `location`
- `stock_lot`
- `stock_move`
- `sales_order_line_lot_allocation`
Fachliche Wirkung:
- die Bestellung wird im ERP gespeichert und mit Lagerbewegung verknuepft
- vorhandene Allokationen derselben `external_ref` werden vor dem Neuimport rueckwaerts gebucht
- nach erfolgreichem Commit werden Label- und Excel-N8N-Flows direkt per HTTP ausgelöst
### 4.3 `db.trigger.sales_order`
Fachliche Aufgabe:
- `shipping_date` aus `order_date` ableiten
- `order.imported` und `order.cancelled.full` in die Outbox schreiben
- `direct`-Bestellungen automatisch mit `DIR-...` Nummer versehen
Liest:
- `sales_order`
- `fn_next_business_day(timestamp)`
Schreibt:
- `sales_order.shipping_date`
- `outbound_webhook_event`
Fachliche Wirkung:
- fehlendes `shipping_date` wird auf den naechsten Werktag gesetzt
- neue Auftraege und Vollstornos werden als Event in die Outbox gelegt
- direkte Auftraege erhalten bei leerer Nummer automatisch einen `DIR-...`-Ref
### 4.4 `db.trigger.sales_order_line`
Fachliche Aufgabe:
- `line_status` aus `qty_cancelled` ableiten
- Partial-Cancel-Event enqueuen
Liest:
- `sales_order_line`
Schreibt:
- `sales_order_line.line_status`
- `outbound_webhook_event`
Fachliche Wirkung:
- `allocated`, `partially_cancelled` und `cancelled` werden deterministisch gesetzt
- ein Wechsel auf `partially_cancelled` erzeugt ein `order.cancelled.partial`-Event
### 4.5 `db.trigger.stock_move`
Fachliche Aufgabe:
- Negativbestand verhindern
- Chargenwechsel bei leerer `current`-Charge ausloesen
Liest:
- `stock_move`
- `stock_lot`
- `v_stock_lot_balance`
Schreibt:
- `stock_lot`
- `outbound_webhook_event`
Fachliche Wirkung:
- `out`-Bewegungen sind nur auf `current`-Chargen erlaubt
- bei `qty_net <= 0` wird die naechste `open`-Charge `current`
- danach wird wieder eine neue `open`-Charge angelegt
### 4.6 `db.trigger.product`
Fachliche Aufgabe:
- neue Produkte sofort chargenfaehig machen
Liest:
- `product`
Schreibt:
- `stock_lot`
Fachliche Wirkung:
- jedes neue Produkt bekommt direkt eine `current`- und eine `open`-Charge
### 4.7 `n8n.adressetikette-erstellen`
Fachliche Aufgabe:
- Lieferadresse entgegennehmen
- Felder normalisieren
- HTML/CSS in PDF/PNG umsetzen
- Ergebnis per SFTP hochladen
Liest:
- Webhook-Input
- Gotenberg / pdf2png / SFTP-Ziele
Schreibt:
- keine ERP-DB
Fachliche Wirkung:
- eigenstaendiger Ausgabekanal fuer Versandetiketten
### 4.8 Outbox-Ablage ueber `outbound_webhook_event`
Fachliche Aufgabe:
- Eventdaten fuer spaetere Auslieferung sammeln
Liest:
- `fn_enqueue_event`
Schreibt:
- `outbound_webhook_event`
Fachliche Wirkung:
- Queue wird im geprüften DEV-Stand befuellt
- ein dazugehoeriger Dispatcher/Worker wurde im Code nicht gefunden
## 5. Technische Einbettung
Die reale Phase-1-Logik bildet einen kleinen operativen Kern mit vier Schwerpunkten:
1. Kontakt- und Adressstamm
2. Bestellimport via n8n -> PHP -> DB
3. Chargen- und Lagerbewegung mit Trigger-Logik
4. Label-Generierung als separater n8n-Ausgang
Wichtige Abweichungen zum alten Zielbild:
- Sellout-Forecast ist im live Schema nicht aktiv.
- Ein Dispatcher fuer `outbound_webhook_event` wurde im geprüften DEV-Baum nicht gefunden.
- Eine separate Direct-Sale-Eingabestrecke wurde im geprüften DEV-Baum nicht gefunden; vorhanden ist nur die DB-/Trigger-Unterstuetzung fuer `order_source = direct`.
## 6. Kurzfazit
Die aktuelle technische Wahrheit von Phase 1 ist auf Nachvollziehbarkeit und direkte Integrationspfade ausgerichtet:
- Bestellungen werden idempotent gespeichert
- Lagerbestand wird ueber Chargen und Bewegungen abgebildet
- Rueckverfolgung erfolgt ueber explizite Allokationen
- n8n importiert und erzeugt Labels direkt
- Outbox-Events werden geschrieben, aber ein Dispatcher ist im geprüften DEV-Stand nicht vorhanden
@@ -0,0 +1,16 @@
# Temporary Migration Artifacts
Stand: 2026-06-15
Diese Artefakte sind eine minimale Kompatibilitaetsschicht, damit die Applikation auch dann unter dem aktuellen Webroot laeuft, wenn der Server noch nicht sauber auf `public/` zeigt.
## Aktuell vorhanden
- Root-`index.php` als Delegation auf `public/index.php`
- Root-`logout.php` als Delegation auf `public/logout.php`
- Root-`assets/styles.css` als Import auf `public/assets/styles.css`
- Root-`assets/help-icon-overlays.js` als Kopie des vorhandenen Helferskripts
## Rueckbau
Sobald der Webroot sauber auf `public/` zeigt, koennen diese Wrapper wieder entfernt werden.
+23
View File
@@ -0,0 +1,23 @@
# Module Ownership Docs
Stand: 2026-06-15
Diese Ordnerstruktur dokumentiert die fachliche und technische Ownership der Module.
Regeln:
- Ein Ordner pro Hauptmodul oder technischem Bereich.
- Jedes Modul bekommt eine kurze README mit Zweck, Ownership, Reads, Writes und Grenzen.
- Submodule werden im README des owning Moduls kurz benannt, wenn sie nicht eigenes Ownership tragen.
- Code, Doku und Datenmodell sollen dieser Struktur folgen.
- Bereits technisch umgesetzt sind aktuell nur:
- `modules/erp/kontakte`
- `modules/erp/bestellungen`
- `modules/erp/lager`
- `modules/erp/artikel-mapping`
- `modules/erp/import-integration`
- `modules/erp/direktverkauf`
- `modules/shared`
- `modules/system`
Referenz:
- [Technische Architektur](../architektur/technical_architecture.md)
- [Modulkarte](../architektur/modulkarte.md)
+34
View File
@@ -0,0 +1,34 @@
# Buchhaltung
Stand: 2026-06-15
## Zweck
Finanzbuchhaltung mit Kontierung, Verbuchung, Offene-Posten und Abschluss.
## Owns
- Buchungssaetze
- Konten
- OP-Positionen
- Steuerdaten
## Writes
- Verbuchung
- OP-Pflege
- Zahlungslauf
- Mahnwesen
- Abschlusslaeufe
## Reads
- Freigegebene ERP-Belege
- Zahlungsdaten
- Kontenplan
## Grenzen
- Keine operative Lagerlogik.
- Keine Beratungslogik.
- Keine UI-Sonderlogik fuer ERP-Prozesse.
+53
View File
@@ -0,0 +1,53 @@
# ERP
Stand: 2026-06-15
## Zweck
ERP ist der operative Container fuer die Kerndomane des Tagesgeschaefts.
## Enthaltene Module
- Kontakte
- Bestellungen
- Lager
- Artikel-Mapping
- Import-Integration
- Direktverkauf
## Ownership
- Owns keine fachlichen Daten als Container.
- Owns nur die Strukturierung der operativen ERP-Module.
- Fachliche Verantwortung liegt immer in den Untermodulen.
## Grenzen
- Keine Buchhaltung.
- Keine Kundenberatung.
- Keine technischen `shared`-Hilfen.
- Keine Runtime-Logik.
## Relevante Entry-Points
- `public/api/otc-order.php`
- `public/otc/index.php`
- `public/order-import.php`
- `public/deploy.php`
## Technisch umgesetzt
- `modules/erp/kontakte/service.php`
- `modules/erp/bestellungen/service.php`
- `modules/erp/lager/service.php`
- `modules/erp/artikel-mapping/service.php`
- `modules/erp/import-integration/order-import.php`
- `modules/erp/import-integration/service.php`
- `modules/erp/direktverkauf/api/otc-order.php`
- `modules/erp/direktverkauf/ui/index.php`
## Wrapper
- `public/api/otc-order.php`
- `public/otc/index.php`
- `public/order-import.php`
- `public/deploy.php`
@@ -0,0 +1,34 @@
# Artikel-Mapping
Stand: 2026-06-15
## Zweck
Aufloesung externer Shopdaten auf interne verkaufbare Artikel und Produkte.
## Owns
- `sellable_item`
- `external_item_alias`
- `sellable_item_component`
## Writes
- Aliaspflege
- Zuordnung externer Artikel
- Komponentenpflege
## Reads
- Bestellungen
- Import-Integration
- Lager
## Grenzen
- Keine Bestellverarbeitung.
- Keine Lagerbewegungen.
- Keine Buchhaltung.
## Technisch umgesetzt
- `modules/erp/artikel-mapping/service.php`
+40
View File
@@ -0,0 +1,40 @@
# Bestellungen
Stand: 2026-06-15
## Zweck
Operative Verarbeitung von Bestellungen, Positionen und Status.
## Owns
- `sales_order`
- `sales_order_line`
- Bestellstatus
## Writes
- Bestellung anlegen und aktualisieren
- Positionen pflegen
- Status fortschreiben
## Reads
- Kontakte
- Artikel-Mapping
- Lager
## Submodule
- Bestellkopf
- Positionen
- Status
## Grenzen
- Keine Buchhaltung.
- Keine Lagerbestandswahrheit.
- Kein Direktverkaufs-UI.
## Technisch umgesetzt
- `modules/erp/bestellungen/service.php`
+44
View File
@@ -0,0 +1,44 @@
# Direktverkauf
Stand: 2026-06-15
## Zweck
Manuelle Erfassung von Direktverkaeufen und deren operative Ausloesung.
## Owns
- Direktverkaufsbelege
- Erfassungsdaten
- OTC-UI und OTC-API
## Writes
- Direktverkauf erfassen
- Folgeschritte im ERP ausloesen
## Reads
- Kontakte optional
- Lager
- Bestellungen
## Relevante Entry-Points
- `public/otc/index.php`
- `public/api/otc-order.php`
## Technisch umgesetzt
- `modules/erp/direktverkauf/api/otc-order.php`
- `modules/erp/direktverkauf/ui/index.php`
## Wrapper
- `public/api/otc-order.php`
- `public/otc/index.php`
## Grenzen
- Keine Buchhaltung.
- Keine allgemeine Bestellverwaltung.
- Keine technische Runtime-Logik.
@@ -0,0 +1,41 @@
# Import-Integration
Stand: 2026-06-15
## Zweck
Technische Annahme externer Eingangsdaten und Weiterleitung an die owning Module.
## Owns
- Import- und Webhook-Einstiege
- technische Importzustandsdaten
- technische Events oder Ausfuehrungsmarker
## Writes
- Importausloesung
- technische Verarbeitungsereignisse
- kontrollierte Weitergabe an Fachmodule
## Reads
- Externe Payloads
- Konfiguration
- Zielmodul-Schnittstellen
## Relevante Entry-Points
- `public/order-import.php`
## Technisch umgesetzt
- `modules/erp/import-integration/order-import.php`
## Wrapper
- `public/order-import.php`
## Grenzen
- Keine Fachlogik fuer Bestellungen, Lager oder Buchhaltung.
- Keine Dateninterpretation, die einem Fachmodul gehoert.
+40
View File
@@ -0,0 +1,40 @@
# Kontakte
Stand: 2026-06-15
## Zweck
Zentraler Kontaktstamm fuer Kunden, Lieferanten und sonstige Parteien.
## Owns
- `party`
- `address`
- `contact`
## Writes
- Kontaktstamm
- Adressen
- Kommunikationsdaten
## Reads
- Bestellungen
- Buchhaltung
- Kundenberatung
## Grenzen
- Keine Bestelllogik.
- Keine Lagerlogik.
- Keine Buchungslogik.
## Relevante Schnittstellen
- Kontaktanlage
- Kontaktabfrage
- Lookup fuer andere Module
## Technisch umgesetzt
- `modules/erp/kontakte/service.php`
+42
View File
@@ -0,0 +1,42 @@
# Lager
Stand: 2026-06-15
## Zweck
Bestand, Charge, MHD und Bewegungen.
## Owns
- `product`
- `stock_lot`
- `stock_move`
- `v_stock_lot_balance`
## Writes
- Warenzugang
- Warenabgang
- Umlagerung
- Chargenpflege
## Reads
- Bestellungen
- Import-Integration
- Direktverkauf
## Submodule
- Chargen
- Bewegungen
- Bestandsfuehrung
## Grenzen
- Keine Buchhaltung.
- Keine Beratungslogik.
- Keine fachfremden Artikelzuordnungen.
## Technisch umgesetzt
- `modules/erp/lager/service.php`
+33
View File
@@ -0,0 +1,33 @@
# Kundenberatung
Stand: 2026-06-15
## Zweck
Fall- und gespraechsbezogene Kundenberatung mit Rueckmeldung und Follow-up.
## Owns
- Beratungsgespraeche
- Empfehlungen
- Rueckmeldungen
- Follow-ups
## Writes
- Gespraech erfassen
- Bedarf dokumentieren
- Empfehlungen pflegen
- Rueckmeldungen nachhalten
## Reads
- Kontakte
- Produkte
- Bestellungen
## Grenzen
- Keine Buchhaltung.
- Keine Lagerbestandswahrheit.
- Keine technische Importlogik.
+33
View File
@@ -0,0 +1,33 @@
# Shared
Stand: 2026-06-15
## Zweck
Fachlich neutrale, wiederverwendbare technische Bausteine.
## Enthalten
- UI-Komponenten
- Layout-Bausteine
- Tabellen, Formulare, Statusdarstellung
- Validierung ohne Fachentscheidung
- Datums-, Geld- und Formatierungshelfer
- technische API-Clients
- Logging- und Session-nahe Hilfen
## Technisch umgesetzt
- `modules/shared/db.php`
- `modules/shared/webhook_throttle.php`
## Ownership
- Keine fachliche Primärverantwortung.
- Keine Business-Logik.
## Grenzen
- Keine Lagerlogik.
- Keine Buchhaltungslogik.
- Keine Beratungslogik.
- Keine Bestellfachlogik.
+32
View File
@@ -0,0 +1,32 @@
# System
Stand: 2026-06-15
## Zweck
Technische Laufzeit-, Start-, Trigger- und Supervisor-Logik.
## Enthalten
- Webhook-/Trigger-Logik
- Start- und Deploy-Hooks
- technische Jobsteuerung
- Laufzeit- und Betriebslogik
## Technisch umgesetzt
- `modules/system/deploy.php`
## Wrapper
- `public/deploy.php`
## Ownership
- Keine fachliche Primärverantwortung.
- Nur technische Infrastruktur- und Runtime-Funktionen.
## Grenzen
- Keine operativen Fachprozesse.
- Keine Modul-Domain-Logik.
- Keine Geschäftsregeln.
+9
View File
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Write",
"Edit",
"Bash"
]
}
}
+1
View File
@@ -0,0 +1 @@
.DS_Store
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+1
View File
@@ -0,0 +1 @@
2026.05.18.1
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-hidden="true">
<path d="m12 2.8 2.89 5.85 6.46.94-4.68 4.56 1.1 6.44L12 17.53 6.23 20.59l1.1-6.44L2.65 9.59l6.46-.94L12 2.8Z" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 228 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-hidden="true">
<path d="m12 2.8 2.89 5.85 6.46.94-4.68 4.56 1.1 6.44L12 17.53 6.23 20.59l1.1-6.44L2.65 9.59l6.46-.94L12 2.8Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 228 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-hidden="true">
<path d="m12 2.8 2.89 5.85 6.46.94-4.68 4.56 1.1 6.44L12 17.53 6.23 20.59l1.1-6.44L2.65 9.59l6.46-.94L12 2.8Z" fill="none" stroke="#000000" stroke-width="1.6" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 285 B

+246
View File
@@ -0,0 +1,246 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Cards</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body class="sg-page-cards">
<h1 class="sg-main-heading">Components Cards</h1>
<!-- ========================================================= -->
<!-- Card Styles -->
<!-- ========================================================= -->
<!-- ========================================================= -->
<!-- Components -->
<!-- ========================================================= -->
<!-- Component: Cards -->
<section id="component-card">
<p class="sg-preview-label">Component: Cards</p>
<div class="sg-preview-area">
<article class="sg-card" data-component="card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Standard Card mit Header und hellem Body-Segment.
</p>
</div>
</article>
<article class="sg-card" data-component="card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header">
<div class="sg-strong">Card 2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Alternative Farbvariante des Header-Segments.
</p>
</div>
</article>
<article class="sg-card" data-component="card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkbrown" data-component-part="card-header">
<div class="sg-strong">Card 3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Kein weißer Trenner zwischen darkbrown und hellem Segment.
</p>
</div>
</article>
</div>
</section>
<!-- Component: Basic Card -->
<section id="component-basic-card">
<p class="sg-preview-label">Component: Basic Card</p>
<div class="sg-preview-area">
<article class="sg-card" data-component="basic-card">
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body">
<p class="sg-body">
Basic Card mit nur einem Segment und hellgrauer Fläche gemäss Token.
</p>
</div>
</article>
</div>
</section>
<!-- Component: Content Card -->
<section id="component-content-card">
<p class="sg-preview-label">Component: Content Card</p>
<div class="sg-preview-area">
<article class="sg-card sg-card--content-card" data-component="content-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Content Card</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer luctus, turpis vel porttitor cursus, nibh justo feugiat sem, sit amet egestas lorem arcu sed augue. Donec sed lorem in urna sagittis.
</p>
</div>
</article>
</div>
</section>
<p class="sg-preview-label">Variante: Dark Content Card</p>
<div class="sg-preview-area">
<article class="sg-card sg-card--content-card sg-card--content-card-dark" data-component="content-card" aria-label="Dark Content Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__two-column" data-pattern-part="text-block-two-column">
<p class="sg-body sg-text-layout-pattern__column">
Footer links: Platzhaltertext fuer allgemeine Hinweise, Navigation oder Kontaktinformationen im zweispaltigen Layout.
</p>
<p class="sg-body sg-text-layout-pattern__column">
Footer rechts: Platzhaltertext fuer ergaenzende Angaben, rechtliche Hinweise oder sekundäre Footer-Inhalte im gleichen Raster.
</p>
</div>
</div>
</article>
</div>
<!-- Component: Group Card -->
<section id="component-group-card">
<p class="sg-preview-label">Component: Group Card</p>
<div class="sg-group-card sg-group-card--margin-after-large" data-component="group-card">
<article class="sg-card" data-component="card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card in Group</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Group Cards bündeln mehrere zusammengehörige Cards.
</p>
</div>
</article>
<article class="sg-card" data-component="card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header">
<div class="sg-strong">Weitere Card</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Die Group Card ist eine eigenständige Component.
</p>
</div>
</article>
</div>
<p class="sg-preview-label">Variante: Group Card mit H2 Überschrift</p>
<div class="sg-group-card sg-group-card--margin-after-large" data-component="group-card">
<div class="sg-group-card__header-row">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading">Gruppenüberschrift</h2>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Gruppenmenü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Gruppenmenü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Gruppenüberschrift anpassen</a>
</div>
</div>
</div>
<article class="sg-card" data-component="card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card in Group</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Group Cards bündeln mehrere zusammengehörige Cards.
</p>
</div>
</article>
<article class="sg-card" data-component="card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen" data-component-part="card-header">
<div class="sg-strong">Weitere Card</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Die Group Card enthält hier zusätzlich eine H2-Überschrift.
</p>
</div>
</article>
</div>
</section>
<!-- Component: Multisegment Card -->
<section id="component-multisegment-card">
<p class="sg-preview-label">Component: Multisegment Card</p>
<div class="sg-preview-area">
<article class="sg-card" data-component="multisegment-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Multisegment Card</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-segment">
<p class="sg-body">Erstes graues Segment mit Textinhalt.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-segment">
<p class="sg-body">Zweites graues Segment mit Textinhalt.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-segment">
<p class="sg-body">Erstes weißes Segment.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-segment">
<p class="sg-body">Zweites weißes Segment.</p>
</div>
</article>
</div>
</section>
<!-- Component: Transparent Card -->
<section id="component-transparent-card">
<p class="sg-preview-label">Component: Transparent Card</p>
<div class="sg-preview-area">
<div class="sg-transparent-card" data-component="transparent-card">
<p class="sg-body">
Transparente Card mit standardisiertem Card-Padding.
</p>
</div>
</div>
</section>
</body>
</html>
+90
View File
@@ -0,0 +1,90 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Charts</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Components Charts</h1>
<section id="component-score-bar">
<p class="sg-preview-label">Component: Score-Balken / Median-Marker</p>
<div class="sg-form-preview-area sg-chart-preview-area">
<div class="sg-score-bar-list" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Äpfel</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Birnen</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Zitronen</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker sg-score-bar__median-marker--outline" data-component-part="score-median-marker" data-component-variant="outline"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Kirschen</p>
<div class="sg-score-bar sg-score-bar--marker-mid sg-score-bar--no-value" role="img" aria-label="Keine Daten" data-component-part="score-track">
<span class="sg-score-bar__empty-state" data-component-part="score-empty-state">keine Daten</span>
</div>
</div>
</div>
</div>
</section>
<section id="pattern-gesamtscore-balken">
<p class="sg-preview-label">Component: Gesamtscore-Balken</p>
<div class="sg-form-preview-area sg-chart-preview-area">
<div class="sg-score-bar-list sg-score-bar-list--single-score" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="warning"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--warning" data-component-part="score-state">abwarten</p>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker sg-score-bar__median-marker--outline" data-component-part="score-median-marker" data-component-variant="outline"></div>
</div>
<p class="sg-bar-label sg-score-state--negative" data-component-part="score-state">unattraktiv</p>
</div>
</div>
</div>
</section>
</body>
</html>
@@ -0,0 +1,231 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Data Display</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Components Data Display</h1>
<!-- ========================================================= -->
<!-- Components -->
<!-- ========================================================= -->
<!-- Component: Data Table -->
<section id="component-data-table">
<p class="sg-preview-label">Component: Data Table</p>
<table class="sg-data-table" aria-label="Beispiel Kennzahlen-Tabelle" data-component="data-table">
<thead>
<tr>
<th data-component-part="data-table-header-cell">Kennzahl</th>
<th data-component-part="data-table-header-cell">Wert</th>
<th data-component-part="data-table-header-cell">Peer-Median</th>
</tr>
</thead>
<tbody>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE <span class="sg-data-table__help-icon" aria-label="Hilfetext" data-component="help-icon" data-component-context="data-table">?</span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">7.7</td>
<td data-component-part="data-table-reference-cell">11.7</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE Forward <span class="sg-data-table__help-icon" aria-label="Hilfetext" data-component="help-icon" data-component-context="data-table">?</span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">8.6</td>
<td data-component-part="data-table-reference-cell">9.7</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PEG Forward <span class="sg-data-table__help-icon" aria-label="Hilfetext" data-component="help-icon" data-component-context="data-table">?</span></td>
<td class="sg-data-table__value sg-data-table__value--warning" data-component-part="data-table-value-cell" data-component-state="warning">1.23</td>
<td data-component-part="data-table-reference-cell">1.43</td>
</tr>
</tbody>
</table>
</section>
<!-- Component: Data Columns -->
<section id="component-data-columns">
<p class="sg-preview-label">Component: Data Columns</p>
<table class="sg-data-table" aria-label="Beispiel Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td class="sg-data-table__value" data-component-part="data-columns-value-cell">7.7</td>
<td data-component-part="data-columns-reference-cell">11.7</td>
</tr>
<tr>
<td class="sg-data-table__value" data-component-part="data-columns-value-cell">8.6</td>
<td data-component-part="data-columns-reference-cell">9.7</td>
</tr>
<tr>
<td class="sg-data-table__value sg-data-table__value--warning" data-component-part="data-columns-value-cell" data-component-state="warning">1.23</td>
<td data-component-part="data-columns-reference-cell">1.43</td>
</tr>
</tbody>
</table>
</section>
<!-- Component: Large Table -->
<section id="component-large-table">
<p class="sg-preview-label">Component: Large Table</p>
<p>Suche, Sortierung und „Mehr laden“ sind hier Demo-Markup. Produktions-Implementierungen müssen Treffer serverseitig neu rendern.</p>
<article class="sg-card sg-large-table" data-component="large-table" role="table" aria-label="Beispiel Large Table">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-large-table__title-segment" data-component-part="large-table-header">
<div class="sg-strong">Large Table</div>
<span class="sg-search-field-row" data-component="search-field">
<span class="sg-input-single-line-wrap" data-has-value="false" data-component="single-line-input" data-component-state="inactive-selectable">
<input
class="sg-interaction-element sg-input-single-line sg-search-field-input sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
placeholder="Suche"
aria-label="Suchfeld ohne Eingabe"
data-large-table-search
>
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen">×</button>
</span>
</span>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row sg-large-table__row--header" role="row" data-component-part="large-table-header-row">
<div class="sg-large-table__cell sg-large-table__cell--header" role="columnheader" aria-sort="none" data-sort-key="0">
<button class="sg-large-table__sort-button" type="button" aria-label="Spalte 1 sortieren">
<span class="sg-large-table__sort-label">Spalte 1</span>
<span class="sg-large-table__sort-icon" aria-hidden="true" data-direction="ascending"></span>
</button>
</div>
<div class="sg-large-table__cell sg-large-table__cell--header" role="columnheader" aria-sort="none" data-sort-key="1">
<button class="sg-large-table__sort-button" type="button" aria-label="Spalte 2 sortieren">
<span class="sg-large-table__sort-label">Spalte 2</span>
<span class="sg-large-table__sort-icon" aria-hidden="true" data-direction="ascending"></span>
</button>
</div>
<div class="sg-large-table__cell sg-large-table__cell--header" role="columnheader" aria-sort="none" data-sort-key="2">
<button class="sg-large-table__sort-button" type="button" aria-label="Spalte 3 sortieren">
<span class="sg-large-table__sort-label">Spalte 3</span>
<span class="sg-large-table__sort-icon" aria-hidden="true" data-direction="ascending"></span>
</button>
</div>
<div class="sg-large-table__cell sg-large-table__cell--header" role="columnheader" aria-sort="none" data-sort-key="3">
<button class="sg-large-table__sort-button" type="button" aria-label="Spalte 4 sortieren">
<span class="sg-large-table__sort-label">Spalte 4</span>
<span class="sg-large-table__sort-icon" aria-hidden="true" data-direction="ascending"></span>
</button>
</div>
<div class="sg-large-table__cell sg-large-table__cell--header" role="columnheader" aria-sort="none" data-sort-key="4">
<button class="sg-large-table__sort-button" type="button" aria-label="Spalte 5 sortieren">
<span class="sg-large-table__sort-label">Spalte 5</span>
<span class="sg-large-table__sort-icon" aria-hidden="true" data-direction="ascending"></span>
</button>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A1</div>
<div class="sg-large-table__cell" role="cell">B1</div>
<div class="sg-large-table__cell" role="cell">C1</div>
<div class="sg-large-table__cell" role="cell">D1</div>
<div class="sg-large-table__cell" role="cell">E1</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A2</div>
<div class="sg-large-table__cell" role="cell">B2</div>
<div class="sg-large-table__cell" role="cell">C2</div>
<div class="sg-large-table__cell" role="cell">D2</div>
<div class="sg-large-table__cell" role="cell">E2</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A3</div>
<div class="sg-large-table__cell" role="cell">B3</div>
<div class="sg-large-table__cell" role="cell">C3</div>
<div class="sg-large-table__cell" role="cell">D3</div>
<div class="sg-large-table__cell" role="cell">E3</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A4</div>
<div class="sg-large-table__cell" role="cell">B4</div>
<div class="sg-large-table__cell" role="cell">C4</div>
<div class="sg-large-table__cell" role="cell">D4</div>
<div class="sg-large-table__cell" role="cell">E4</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A5</div>
<div class="sg-large-table__cell" role="cell">B5</div>
<div class="sg-large-table__cell" role="cell">C5</div>
<div class="sg-large-table__cell" role="cell">D5</div>
<div class="sg-large-table__cell" role="cell">E5</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A6</div>
<div class="sg-large-table__cell" role="cell">B6</div>
<div class="sg-large-table__cell" role="cell">C6</div>
<div class="sg-large-table__cell" role="cell">D6</div>
<div class="sg-large-table__cell" role="cell">E6</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A7</div>
<div class="sg-large-table__cell" role="cell">B7</div>
<div class="sg-large-table__cell" role="cell">C7</div>
<div class="sg-large-table__cell" role="cell">D7</div>
<div class="sg-large-table__cell" role="cell">E7</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A8</div>
<div class="sg-large-table__cell" role="cell">B8</div>
<div class="sg-large-table__cell" role="cell">C8</div>
<div class="sg-large-table__cell" role="cell">D8</div>
<div class="sg-large-table__cell" role="cell">E8</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A9</div>
<div class="sg-large-table__cell" role="cell">B9</div>
<div class="sg-large-table__cell" role="cell">C9</div>
<div class="sg-large-table__cell" role="cell">D9</div>
<div class="sg-large-table__cell" role="cell">E9</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row" role="row" data-component-part="large-table-row">
<div class="sg-large-table__cell" role="cell">A10</div>
<div class="sg-large-table__cell" role="cell">B10</div>
<div class="sg-large-table__cell" role="cell">C10</div>
<div class="sg-large-table__cell" role="cell">D10</div>
<div class="sg-large-table__cell" role="cell">E10</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row sg-large-table__row--load-more" role="row" data-component-part="large-table-row" data-large-table-load-more-row="true">
<div class="sg-large-table__cell sg-large-table__cell--load-more" role="cell">
<div class="sg-navigation-card-layout sg-large-table__load-more-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink" data-large-table-load-more-trigger="true">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
</div>
</div>
</article>
</section>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Typography</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Components Typography</h1>
<section id="component-typography">
<p class="sg-preview-label">Component: Typography</p>
<div class="sg-typography-preview" data-component="typography-preview">
<p class="sg-brand-title" data-component-part="brand-title">Portal Titel</p>
<h1 class="sg-heading-h1" data-component-part="heading-h1">H1 Überschrift</h1>
<h2 class="sg-heading-h2" data-component-part="heading-h2">H2 Überschrift</h2>
<p class="sg-body" data-component-part="body-text">Body / Fließtext</p>
<p class="sg-strong" data-component-part="strong-text">Strong / hervorgehobener Text</p>
<p class="sg-section-title" data-component-part="section-title">Section Title</p>
<p class="sg-bar-label" data-component-part="bar-label">Bar Label</p>
<p class="sg-table-label" data-component-part="table-label">Table Label</p>
<p class="sg-table-value" data-component-part="table-value">Table Value</p>
</div>
</section>
</body>
</html>
+44
View File
@@ -0,0 +1,44 @@
# Portal Sync Flow
## Ziel
Der Styleguide bleibt in diesem Repository die Source of Truth. Der Sync verteilt den freigegebenen Stand in beide Portal-Repos:
- den deploy-relevanten CSS-Upstream versioniert als `public/assets/styleguide.upstream.css`
- die vollstaendige Styleguide-Dokumentation gespiegelt nach `docs/styleguide`
- die portal-spezifische Fertig-CSS-Datei als `public/assets/styles.css`
Zielrepos:
- `WebApp_Aktienberater`
- `erp_naurua`
## Vorbereitung
- Version in `VERSION` erhoehen, sobald ein freigegebener Stand vorliegt.
- Aenderungen im Styleguide committen und pushen.
## Sync ausfuehren
Beispiel:
```bash
./scripts/sync_styleguide_to_webapp_aktienberater.sh \
--commit-portal
```
## Ergebnis im Portalrepo
- `public/assets/styleguide.upstream.css` aktualisiert
- `public/assets/styleguide.upstream.meta.json` aktualisiert (Version, Commit, Zeitstempel)
- `docs/styleguide` gespiegelt (mit `--delete`, ohne `.git`, `.codex`, `AGENTS.md`, `scripts/`)
- `public/assets/styles.css` aktualisiert
- Optional: automatischer Commit + Push im Portalrepo
## Standardprozess je Release
1. Styleguide aendern
2. `VERSION` erhoehen
3. Styleguide commit + push
4. Sync-Skript ausfuehren
5. Beide Portalrepos Smoke-Testen
+216
View File
@@ -0,0 +1,216 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Foundations</title>
<link rel="stylesheet" href="./styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Foundations</h1>
<section id="foundations-colors">
<h2 class="sg-sub-heading sg-text-on-dark">Colors</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>darkblue</td><td>#4661A9</td><td>Primäre Card-Kopfsegmente, Portalheader, Toggle-Handle sowie primäre Chart-Werte ohne Positiv-Negativ-Darstellung.</td></tr>
<tr><td>darkgreen</td><td>#4D646E</td><td>Alternative Card-Kopfsegmente.</td></tr>
<tr><td>darkbrown</td><td>#665F57</td><td>Gedämpfte alternative Card-Kopfsegmente.</td></tr>
<tr><td>light-grey</td><td>#E2E5E9</td><td>Card-Flächen, Card-Bodies, Formularflächen, Optionszeilen, inaktive Buttons, Sandwich-Menü-Flächen und Chart-/Score-Hintergründe.</td></tr>
<tr><td>medium-grey</td><td>#D4D6DB</td><td>Aktive Standardbuttons, geöffnete Pulldown-Flächen, Hilfetext-Flächen, Fragezeichen-Icons, Referenzwerte in Charts sowie Entfernen-Button in Eingabefeldern.</td></tr>
<tr><td>dark-grey</td><td>#7B879D</td><td>Ausgewählte Tab-Buttons als Fläche sowie Schriftfarbe für deaktivierte oder zurückgenommene Interaktionselemente.</td></tr>
<tr><td>black</td><td>#000000</td><td>Neutrale Vollton-Overlay-Farbe für starke visuelle Deaktivierung von Hintergrundobjekten.</td></tr>
<tr><td>white</td><td>#FFFFFF</td><td>Standardfläche von Buttons, Eingabefeldern, Pulldown-Triggern, Checkboxen, Radios, Toggle-Tracks und Card-Trennern.</td></tr>
<tr><td>signal-green</td><td>#009101</td><td>Positive Score-Balken und positive Chart-Werte.</td></tr>
<tr><td>signal-yellow</td><td>#9C7A00</td><td>Neutrale Score-Balken, neutrale Chart-Werte und Warnwerte in Tabellen.</td></tr>
<tr><td>signal-red</td><td>#9B3B2F</td><td>Negative Score-Balken und negative Chart-Werte.</td></tr>
<tr><td>font-dark</td><td>#414959</td><td>Standard-Schrift auf hellen Flächen, Achsen/Marker in Charts, Sandwich-Linien und Radio-Markierung.</td></tr>
<tr><td>font-light</td><td>#FFFFFF</td><td>Schrift auf dunklen Flächen, Portalheader, Card-Headern, Prozessbuttons und ausgewählten Tab-Buttons.</td></tr>
<tr><td>font-hyperlink</td><td>#FF6900</td><td>Links in Texten, Prozessbuttons und Page-Navigations-Cards.</td></tr>
<tr><td>process-inactive</td><td>#FFAE79</td><td>Inaktive Hintergrundfläche des Prozessbuttons.</td></tr>
<tr><td>background-purple</td><td>#373C4A</td><td>Globaler Portalhintergrund.</td></tr>
<tr><td>background-purple-light</td><td>#656C7D</td><td>Group Cards und gruppierende Card-Flächen.</td></tr>
<tr><td>transparent</td><td>transparent</td><td>Transparenter Foundation-Wert für semantische Overlay-/Box-Varianten ohne Eigenfläche.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-states">
<h2 class="sg-sub-heading sg-text-on-dark">States</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>form-area</td><td>background: light-grey</td><td>Hintergrund von Formularbereichen, Optionszeilen und Formular-Preview-Flächen. Die Fläche gehört nicht zum einzelnen Formularelement.</td></tr>
<tr><td>form-active</td><td>background: white; color: font-dark</td><td>Aktive oder ausgewählte Eingabefelder, Checkboxen, Radios, Pulldown-Trigger und Pulldown-Optionen. Bei Pulldowns zeigt die Zahl in Klammern die Anzahl gewählter Optionen.</td></tr>
<tr><td>form-inactive-selectable</td><td>background: white; color: font-dark</td><td>Nicht aktive, aber auswählbare Formularfelder, Checkboxen, Radios und Pulldown-Trigger. Dieser Zustand nutzt keine Transparenz.</td></tr>
<tr><td>form-disabled</td><td>background: white; color: dark-grey; opacity: disabled-opacity</td><td>Fachlich nicht verfügbare oder technisch deaktivierte Formularoptionen. Dieser Zustand ist nicht bedienbar.</td></tr>
<tr><td>pulldown-panel-open</td><td>background: medium-grey</td><td>Geöffnete Pulldown-Fläche. Das Panel öffnet unter dem Trigger, schließt bei Klick außerhalb und richtet sich bei Bildschirmrand automatisch links oder rechts aus.</td></tr>
<tr><td>overlay-panel-open</td><td>background: medium-grey oder light-grey je nach Komponente</td><td>Ausklappbare Overlays wie Pulldown-Panels, Hilfetexte und Sandwich-Menüs. Es ist immer nur das jeweils relevante Overlay geöffnet.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-typography">
<h2 class="sg-sub-heading sg-text-on-dark">Typography</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>body</td><td>Open Sans, 1rem, Regular</td><td>Standardtext im Portal.</td></tr>
<tr><td>brand</td><td>Open Sans, 1.6rem, Regular</td><td>Portal-Brand im Portal Header.</td></tr>
<tr><td>h1</td><td>Open Sans, 1.5rem, Regular</td><td>Hauptüberschriften; kleiner als Portal-Brand und 50% größer als Fließtext.</td></tr>
<tr><td>h2</td><td>Open Sans, 1.25rem, Regular</td><td>Zwischenüberschriften; kleiner als H1 und 25% größer als Fließtext.</td></tr>
<tr><td>strong</td><td>Open Sans, 1rem, Semibold</td><td>Hervorgehobener Text, Labels und Button-Text.</td></tr>
<tr><td>section-title</td><td>Open Sans, 1rem, Regular</td><td>Section-Titel; aktuell identisch mit body, aber separat benannt.</td></tr>
<tr><td>bar-label</td><td>Open Sans, 1rem, Semibold</td><td>Labels bei Score-Balken.</td></tr>
<tr><td>table-label</td><td>Open Sans, 0.8rem, Regular</td><td>Tabellen- und Grafikbeschriftungen sowie kurze Hilfetexte und Tooltip-Inhalte.</td></tr>
<tr><td>table-value</td><td>Open Sans, 0.8rem, Semibold</td><td>Hervorgehobene Tabellen- und Grafikwerte.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-portal-overrides">
<h2 class="sg-sub-heading sg-text-on-dark">Naurua Overrides</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Portal</th>
<th>Abweichung</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>NAURUA</td><td>darkblue → #354A52; body → Avenir, sans-serif</td><td>Portalgebundene Foundation-Overrides für das zweite Portal. Die Überschreibung greift nur, wenn das Root-Element `data-portal="naurua"` setzt.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-spacing">
<h2 class="sg-sub-heading sg-text-on-dark">Spacing</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>spacing-small</td><td>0.3rem</td><td>Kleine Abstände zwischen Interaktionselementen, Card-Segmenten, Pulldown-Optionen, Overlay-Triggern und eng zusammengehörigen Cards.</td></tr>
<tr><td>spacing-large</td><td>1rem</td><td>Große Abstände zwischen Cards und Gruppen.</td></tr>
<tr><td>card-segment-padding</td><td>0.75rem vertikal / 1rem horizontal (Desktop), 0.5rem horizontal (Mobile)</td><td>Innenabstand von Card-Segmenten; auf Mobile wird das horizontale Padding reduziert.</td></tr>
<tr><td>interaction-padding</td><td>0.25rem vertikal / 1rem horizontal</td><td>Innenabstand von Interaktionselementen.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-dimensions">
<h2 class="sg-sub-heading sg-text-on-dark">Dimensions</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>interaction-height</td><td>2rem</td><td>Einheitliche Höhe für Buttons, Tabs, Pulldowns, Eingabefelder und Sandwich-Menü-Buttons.</td></tr>
<tr><td>compact-interaction-height</td><td>1.5rem</td><td>Einheitliche Höhe für schmale Interaktionselemente wie Tasten-Navigation-schmal.</td></tr>
<tr><td>compact-interaction-padding</td><td>0.15rem vertikal / 0.75rem horizontal</td><td>Innenabstand schmaler Interaktionselemente.</td></tr>
<tr><td>small-interaction-element-size</td><td>1.25rem</td><td>Eigene Höhe und Breite kleiner Interaktionselemente wie Fragezeichen-Icon, Checkboxen und Radio Buttons.</td></tr>
<tr><td>disabled-opacity</td><td>0.45</td><td>Deckkraft für fachlich nicht verfügbare oder technisch deaktivierte Interaktionselemente.</td></tr>
<tr><td>sandwich-line-width</td><td>1.25rem</td><td>Länge der Striche im Sandwich-Menü-Button.</td></tr>
<tr><td>sandwich-line-height</td><td>4px</td><td>Dicke der Striche im Sandwich-Menü-Button.</td></tr>
<tr><td>sandwich-line-gap</td><td>3px</td><td>Abstand zwischen den Strichen im Sandwich-Menü-Button.</td></tr>
<tr><td>score-bar-height</td><td>1rem</td><td>Höhe horizontaler Score-Balken.</td></tr>
<tr><td>score-marker-width</td><td>6px</td><td>Breite des Median-Markers.</td></tr>
<tr><td>score-marker-height</td><td>score-bar-height + 2px</td><td>Median-Marker ragt oben und unten je 1px über den Score-Balken hinaus.</td></tr>
<tr><td>chart-height-bar</td><td>24rem</td><td>Basis-Höhe für Bar Charts; in der Component wird sie proportional reduziert, die Chart-Höhe skaliert nicht mit der Breite.</td></tr>
<tr><td>chart-height-line</td><td>18rem</td><td>Basis-Höhe für Line Charts; in der Component wird sie proportional reduziert, die Chart-Höhe skaliert nicht mit der Breite.</td></tr>
<tr><td>chart-axis-label-column-width</td><td>4rem</td><td>Breite der Y-Achsenbeschriftungsspalte bei Charts.</td></tr>
<tr><td>chart-axis-label-gap</td><td>5px</td><td>Horizontaler Abstand zwischen Y-Achsenbeschriftung und Y-Achse.</td></tr>
<tr><td>chart-grid-line-width</td><td>1px</td><td>Linienstärke von Gridlines und Achsen in Charts.</td></tr>
<tr><td>chart-line-width</td><td>2px</td><td>Linienstärke der Datenlinie im Line Chart.</td></tr>
<tr><td>input-label-width</td><td>9rem</td><td>Desktop-Breite der Label-Spalte für gruppierte Formularzeilen mit Pulldowns, Slidern, Radio-Feldern, Checkbox-Feldern sowie ein- und mehrzeiligen Eingabefeldern.</td></tr>
<tr><td>input-field-desktop-width</td><td>400px</td><td>Fixe Desktop-Breite von ein- und mehrzeiligen Eingabefeldern in der Input-Component-Preview.</td></tr>
<tr><td>input-field-max-width</td><td>600px</td><td>Maximale Breite von ein- und mehrzeiligen Eingabefeldern (ohne Suchfeld).</td></tr>
<tr><td>object-card-height</td><td>600px</td><td>Fixe Höhe der Object Card im Desktop-Layout (u. a. für Company Card).</td></tr>
<tr><td>content-card-margin-top-desktop</td><td>100px</td><td>Oberer Außenabstand der Content Card auf Desktop.</td></tr>
<tr><td>content-card-margin-top-mobile</td><td>1rem</td><td>Oberer Außenabstand der Content Card auf Mobile; entspricht spacing-large.</td></tr>
<tr><td>object-group-card-min-width</td><td>450px</td><td>Mindestbreite der Group Card im Pattern Object Group Card.</td></tr>
<tr><td>object-group-card-max-width</td><td>650px</td><td>Maximale Breite der Group Card im Pattern Object Group Card.</td></tr>
<tr><td>object-group-card-height</td><td>700px</td><td>Fixe Desktop-Höhe der Group Card im Pattern Object Group Card.</td></tr>
<tr><td>delete-confirmation-overlay-offset-block-start</td><td>10%</td><td>Vertikaler Abstand des Overlays vom oberen Rand des Zielobjekts, relativ zur Objektgröße.</td></tr>
<tr><td>delete-confirmation-overlay-max-height</td><td>calc(100vh - 10vh)</td><td>Maximalhöhe des Overlays relativ zum Viewport; danach scrollt der Inhalt innerhalb des Overlays.</td></tr>
<tr><td>notifications-card-min-width</td><td>445px</td><td>Mindestbreite der Notification Card im Pattern Notifications; 50px größer als die Object Card und Grundlage für Flex-Basis und Mindestbreite des Patterns.</td></tr>
<tr><td>search-field-width</td><td>15.3rem</td><td>Fixe Breite des Suchfeld-Inputs inklusive Clear-Button-Bereich.</td></tr>
<tr><td>layer-pulldown-panel</td><td>40</td><td>Layer-Stufe für geöffnete Pulldown-Ausklappfelder, damit sie über anderen Inhalten liegen.</td></tr>
<tr><td>multiselect-pulldown-panel-desktop-width</td><td>500px</td><td>Reservierte Foundation-Breite für Multiselektions-Pulldown-Panels; aktuell nicht aktiv genutzt, da das Panel inhaltsbasiert mit Viewport-Kappe skaliert.</td></tr>
<tr><td>multiselect-pulldown-panel-mobile-width</td><td>80vw</td><td>Reservierte mobile Foundation-Breite für Multiselektions-Pulldown-Panels; aktuell nicht aktiv genutzt, da das Panel inhaltsbasiert mit Viewport-Kappe skaliert.</td></tr>
<tr><td>options-row-mode-toggle-width</td><td>7rem</td><td>Breite des Modus-Schiebers in der Options Row.</td></tr>
<tr><td>options-row-help-panel-width</td><td>16rem</td><td>Breite des Help-Panels in der Options Row.</td></tr>
<tr><td>card-list-drawer-width</td><td>40%</td><td>Relative Breite des ausziehbaren Card-Listenbereichs.</td></tr>
<tr><td>notifications-text-segment-fixed-height</td><td>150px</td><td>Desktop-Höhe des ersten Text-Segments im Standard-Pattern Notifications.</td></tr>
<tr><td>notifications-text-segment-fixed-height-small</td><td>80px</td><td>Desktop-Höhe des ersten Text-Segments in der Variante Pattern Notifications small.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-radius">
<h2 class="sg-sub-heading sg-text-on-dark">Radius</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>radius-card</td><td>8px</td><td>Cards, Group Cards, Formularflächen, Optionszeilen und geöffnete Overlay-Flächen.</td></tr>
<tr><td>radius-interaction</td><td>4px</td><td>Buttons, Eingabefelder, Pulldown-Trigger, Checkboxen und Modus-Schieber.</td></tr>
<tr><td>radius-graph-bar</td><td>2px</td><td>Score-Balken und Graphik-Balken.</td></tr>
</tbody>
</table>
</section>
<section id="foundations-shadows-borders">
<h2 class="sg-sub-heading sg-text-on-dark">Shadows / Borders</h2>
<table class="sg-foundation-table sg-table-label">
<thead>
<tr>
<th>Name</th>
<th>Wert</th>
<th>Verwendung</th>
</tr>
</thead>
<tbody>
<tr><td>shadow-none</td><td>none</td><td>Kein Schatten für nicht-overlay Flächen.</td></tr>
<tr><td>shadow-overlay</td><td>0 10px 24px rgba(0, 0, 0, 0.22)</td><td>Standard-Schatten für aufklappbare Flächen wie Pulldown-, Menü- und Help-Panels.</td></tr>
<tr><td>border-none</td><td>none</td><td>Es werden keine Borders verwendet.</td></tr>
</tbody>
</table>
</section>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portal Styleguide</title>
<link rel="stylesheet" href="./styleguide.css">
</head>
<body>
<main class="sg-index">
<h1 class="sg-main-heading">Portal Styleguide</h1>
<section class="sg-index-section">
<h2 class="sg-sub-heading">Foundations</h2>
<ul class="sg-index-list">
<li><a href="./foundations.html">Foundations</a></li>
<li><a href="./semantic-tokens-components.html">Semantic Tokens Components</a></li>
<li><a href="./semantic-tokens-patterns.html">Semantic Tokens Patterns</a></li>
<li><a href="./semantic-tokens-layouts.html">Semantic Tokens Layouts</a></li>
</ul>
</section>
<section class="sg-index-section">
<h2 class="sg-sub-heading">Components</h2>
<ul class="sg-index-list">
<li><a href="./components/cards.html">Cards</a></li>
<li><a href="./components/interactive-elements.html">Interactive Elements</a></li>
<li><a href="./components/charts.html">Charts</a></li>
<li><a href="./components/data-display.html">Data Display</a></li>
<li><a href="./components/typography.html">Typography</a></li>
</ul>
</section>
<section class="sg-index-section">
<h2 class="sg-sub-heading">Patterns</h2>
<ul class="sg-index-list">
<li><a href="./patterns/portal-header.html">Portal Header</a></li>
<li><a href="./patterns/vsf-portal-header-public.html">VSF Portal Header Public</a></li>
<li><a href="./patterns/options-row.html">Options Row</a></li>
<li><a href="./patterns/object-card.html">Object Card</a></li>
<li><a href="./patterns/object-group-card.html">Object Group Card</a></li>
<li><a href="./patterns/navigation-card.html">Navigation Card</a></li>
<li><a href="./patterns/left-navigation.html">Left Navigation</a></li>
<li><a href="./patterns/notifications.html">Notifications</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/formular-mit-abschnitten.html">Formular mit Abschnitten</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/delete-confirmation-overlay.html">Overlay Card</a></li>
</ul>
</section>
<section class="sg-index-section">
<h2 class="sg-sub-heading">Layouts</h2>
<h3 class="sg-sub-heading sg-section-h3">Generische Layouts</h3>
<ul class="sg-index-list">
<li><a href="./patterns/card-listen-seite.html">Card Listen Seite</a></li>
<li><a href="./patterns/page-layout-app.html">Page Layout App</a></li>
<li><a href="./patterns/page-layout-public.html">Page Layout Public</a></li>
<li><a href="./patterns/card-listen-fundamentalanalyse-mobile.html">Card Listen Fundamentalanalyse Mobile</a></li>
</ul>
<h3 class="sg-sub-heading sg-section-h3">Valuestockfinder Layouts</h3>
<ul class="sg-index-list">
<li><a href="./patterns/company-card.html">VSF Layout Company Card</a></li>
<li><a href="./patterns/vsf-list-card.html">VSF List Card</a></li>
<li><a href="./patterns/vsf-card-listen-seite.html">VSF Card Listen Seite</a></li>
<li><a href="./patterns/vsf-list-detailseite.html">VSF List Detailseite</a></li>
<li><a href="./patterns/vsf-meldungen.html">VSF Meldungen</a></li>
<li><a href="./patterns/vsf-register-step-1.html">VFS Keycloak Login</a></li>
<li><a href="./patterns/vsf-listen-uebersicht-seite-v2.html">VSF Listen Übersicht Seite V2</a></li>
<li><a href="./patterns/vsf-card-listen-fundamentalanalyse-mobile.html">VSF Card Listen Fundamentalanalyse Mobile</a></li>
<li><a href="./patterns/vsf-card-listen-fundamentalanalyse-drawer.html">VSF Card Listen Fundamentalanalyse Drawer</a></li>
</ul>
</section>
</main>
</body>
</html>
@@ -0,0 +1,243 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Card Gruppe mit Tastennavigation</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Card Gruppe mit Tastennavigation</h1>
<section id="pattern-card-gruppe-mit-tastennavigation">
<p class="sg-preview-label">Pattern: Card Gruppe mit Tastennavigation</p>
<div class="sg-content-block-card-group" data-pattern="card-gruppe-mit-tastennavigation">
<div class="sg-tab-button-group sg-content-block-card-group__tabs" role="tablist" aria-label="Tasten Navigation" data-component="tab-navigation" data-component-size="large">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="true" aria-controls="content-block-card-1" id="content-block-tab-1" data-component-part="tab-button">Taste 1</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="content-block-card-2" id="content-block-tab-2" data-component-part="tab-button">Taste 2</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="content-block-card-3" id="content-block-tab-3" data-component-part="tab-button">Taste 3</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="content-block-card-4" id="content-block-tab-4" data-component-part="tab-button">Taste 4</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="content-block-card-5" id="content-block-tab-5" data-component-part="tab-button">Taste 5</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="content-block-card-6" id="content-block-tab-6" data-component-part="tab-button">Taste 6</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="content-block-card-7" id="content-block-tab-7" data-component-part="tab-button">Taste 7</button>
</div>
<div class="sg-content-block-card-group__panels">
<div class="sg-content-block-card-group__panel" id="content-block-card-1" role="tabpanel" aria-labelledby="content-block-tab-1">
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 1">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 1.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 1.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 1.2">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 1.2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 1.3">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 1.3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="content-block-card-2" role="tabpanel" aria-labelledby="content-block-tab-2" hidden>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 2">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 2.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 2.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Quis risus sed vulputate odio ut enim blandit volutpat maecenas volutpat blandit aliquam etiam erat velit.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 2.2">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 2.2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Amet cursus sit amet dictum sit amet justo donec enim diam vulputate ut pharetra sit amet aliquam id diam.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 2.3">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 2.3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Magna eget est lorem ipsum dolor sit amet consectetur adipiscing elit ut aliquam purus sit amet luctus.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="content-block-card-3" role="tabpanel" aria-labelledby="content-block-tab-3" hidden>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 3">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 3.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 3.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 3.2">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 3.2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Etiam sit amet nisl purus in mollis nunc sed id semper risus in hendrerit gravida rutrum quisque.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 3.3">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 3.3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Non tellus orci ac auctor augue mauris augue neque gravida in fermentum et sollicitudin ac orci phasellus.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="content-block-card-4" role="tabpanel" aria-labelledby="content-block-tab-4" hidden>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 4">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 4.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 4.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Inhalt für Taste 4.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="content-block-card-5" role="tabpanel" aria-labelledby="content-block-tab-5" hidden>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 5">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 5.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 5.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Inhalt für Taste 5.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="content-block-card-6" role="tabpanel" aria-labelledby="content-block-tab-6" hidden>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 6">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 6.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 6.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Inhalt für Taste 6.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="content-block-card-7" role="tabpanel" aria-labelledby="content-block-tab-7" hidden>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Taste 7">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Card 7.1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Card 7.1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Inhalt für Taste 7.</p>
</div>
</article>
</div>
</div>
</div>
</div>
</section>
<script>
const tabGroup = document.querySelector('[data-pattern="card-gruppe-mit-tastennavigation"]');
if (tabGroup) {
const tabs = Array.from(tabGroup.querySelectorAll('[role="tab"]'));
const panels = Array.from(tabGroup.querySelectorAll('[role="tabpanel"]'));
const tabList = tabGroup.querySelector('[role="tablist"]');
const applyMobileBalancedTabRows = () => {
if (!tabList) {
return;
}
tabs.forEach((tab) => {
tab.style.removeProperty('--sg-tab-mobile-row-slots');
});
if (window.matchMedia('(min-width: 768px)').matches || tabs.length <= 3) {
return;
}
const maxItemsPerRow = 3;
const rowCount = Math.ceil(tabs.length / maxItemsPerRow);
const baseRowSize = Math.floor(tabs.length / rowCount);
const rowRemainder = tabs.length % rowCount;
let tabStartIndex = 0;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
const rowSize = baseRowSize + (rowIndex < rowRemainder ? 1 : 0);
for (let itemOffset = 0; itemOffset < rowSize; itemOffset += 1) {
const tab = tabs[tabStartIndex + itemOffset];
if (tab) {
tab.style.setProperty('--sg-tab-mobile-row-slots', String(rowSize));
}
}
tabStartIndex += rowSize;
}
};
const activateTab = (targetTab) => {
tabs.forEach((tab) => {
tab.setAttribute('aria-selected', String(tab === targetTab));
});
panels.forEach((panel) => {
panel.hidden = panel.id !== targetTab.getAttribute('aria-controls');
});
};
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
activateTab(tab);
});
tab.addEventListener('keydown', (event) => {
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') {
return;
}
event.preventDefault();
const direction = event.key === 'ArrowRight' ? 1 : -1;
const nextIndex = (index + direction + tabs.length) % tabs.length;
const nextTab = tabs[nextIndex];
nextTab.focus();
activateTab(nextTab);
});
});
applyMobileBalancedTabRows();
window.addEventListener('resize', applyMobileBalancedTabRows);
}
</script>
</body>
</html>
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Card Listen Seite Fundamentalanalyse Mobile</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout Fundamentalanalyse Mobile</h1>
<section class="sg-card-list-page" aria-label="Fundamentalanalyse Seite mobil">
<div class="sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigation Zurück zur Liste oben">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="./card-listen-seite.html" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Fundamentalanalyse Karten Gruppe mobil">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Fundamentalanalyse Box 1 mobil">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum. Integer feugiat, sem a iaculis lacinia, augue libero pretium orci, in dictum eros nibh et risus. Fusce sagittis, dolor ut facilisis tincidunt, lorem nisi sodales sem.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Fundamentalanalyse Box 2 mobil">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum. Integer feugiat, sem a iaculis lacinia, augue libero pretium orci, in dictum eros nibh et risus. Fusce sagittis, dolor ut facilisis tincidunt, lorem nisi sodales sem.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Fundamentalanalyse Box 3 mobil">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum. Integer feugiat, sem a iaculis lacinia, augue libero pretium orci, in dictum eros nibh et risus. Fusce sagittis, dolor ut facilisis tincidunt, lorem nisi sodales sem.</p>
</div>
</article>
</div>
<div class="sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigation Zurück zur Liste unten">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="./card-listen-seite.html" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
</section>
</body>
</html>
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF Layout Company Card</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">VSF Layout Company Card</h1>
<section id="layout-company-card">
<p class="sg-preview-label">Layout: Company Card</p>
<p class="sg-body">Dieses Layout basiert auf der Object Card und zeigt initial eine einzelne Company Card Instanz.</p>
<div class="sg-object-card-grid">
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-company-card__header-title">
<div class="sg-strong">Netflix, Inc.</div>
<span class="sg-company-card__header-star" aria-hidden="true"></span>
</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__three-column-distributed" aria-label="Company Kennzahlen dreispaltig verteilt" data-pattern-part="company-card-metrics-three-column">
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-left">
PE: <span class="sg-data-table__value">28.8</span>
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-center">
PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span>
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-right">
PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span>
</p>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
</div>
</section>
<script>
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
const panel = wrap.querySelector('.sg-sandwich-menu-panel');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
if (!nextState || !panel) {
return;
}
wrap.dataset.align = 'right';
const panelRect = panel.getBoundingClientRect();
if (panelRect.left < 0) {
wrap.dataset.align = 'left';
}
});
});
document.addEventListener('click', (event) => {
if (event.target.closest('.sg-sandwich-menu-wrap')) {
return;
}
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,48 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Content Cards Group</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Content Cards Group</h1>
<section id="pattern-content-cards-group">
<p class="sg-preview-label">Pattern: Content Cards Group</p>
<div class="sg-preview-area">
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Gruppe aus Content Cards">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Content Card 1">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Content Card 2">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Content Card 3">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum.</p>
</div>
</article>
</div>
</div>
</section>
</body>
</html>
@@ -0,0 +1,521 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Overlay Card</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern - Overlay Card</h1>
<section id="pattern-overlay-card" class="sg-delete-confirmation-pattern">
<p class="sg-preview-label">Pattern: Overlay Card</p>
<div class="sg-delete-confirmation-pattern__stage sg-delete-confirmation-pattern__host sg-object-card" data-pattern="overlay-card" data-dialog-open="false" data-overlay-confirmation-value="DELETE">
<article class="sg-card sg-object-card sg-delete-confirmation-pattern__target" aria-label="Zu löschendes Objekt">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header">
<div class="sg-strong">Alcon Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#!" data-overlay-open-dialog="delete">Objekt löschen</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content">
<p class="sg-body">Objekt-Inhalt der Card. Während das Löschfenster sichtbar ist, wird dieses Objekt um 50% ausgegraut.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Löschbestätigung" role="dialog" aria-modal="true" aria-labelledby="delete-confirmation-title" data-overlay-dialog="delete" hidden>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<p class="sg-body sg-delete-confirmation-pattern__text" id="delete-confirmation-title"><strong>Möchtest du Alcon Inc. wirklich löschen?</strong></p>
<p class="sg-body sg-delete-confirmation-pattern__text">Du kannst das nicht rückgängig machen. Bestätige durch Eingabe von <span class="sg-delete-confirmation-pattern__code">DELETE</span>.</p>
<label class="sg-labeled-input-row sg-delete-confirmation-pattern__input-row">
<span class="sg-label">Bestätigung</span>
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
placeholder="DELETE"
aria-label="Löschbestätigung durch DELETE"
data-overlay-confirmation-input
>
</label>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-close>Abbrechen</button>
<button
class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive"
type="button"
disabled
aria-disabled="true"
data-overlay-confirmation-submit
data-overlay-dialog-close
>
Löschen
</button>
</div>
</div>
</article>
</div>
</section>
<section id="pattern-add-to-list-overlay" class="sg-delete-confirmation-pattern">
<p class="sg-preview-label">Pattern: Add to List Overlay</p>
<div class="sg-delete-confirmation-pattern__stage sg-delete-confirmation-pattern__host sg-object-card" data-pattern="add-to-list-overlay" data-dialog-open="false" data-overlay-selected-lists="1,2">
<article class="sg-card sg-object-card sg-delete-confirmation-pattern__target" aria-label="Zu einer Liste hinzuzufügendes Objekt">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header">
<div class="sg-strong">Alcon Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Zur Liste hinzufügen" data-component-part="sandwich-trigger" data-overlay-open-dialog="add-to-list">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content">
<p class="sg-body">Objekt-Inhalt der Card. Während das Overlay sichtbar ist, wird dieses Objekt um 50% ausgegraut.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-card--overlay-host sg-object-card sg-object-card--variable-height sg-delete-confirmation-pattern__floating-card" aria-label="Zur Liste hinzufügen" role="dialog" aria-modal="true" aria-labelledby="add-to-list-title" data-overlay-dialog="add-to-list" hidden>
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header">
<p class="sg-body sg-strong" id="add-to-list-title">Füge das Unternehmen einer Liste hinzu</p>
</header>
<div class="sg-delete-confirmation-pattern__scroll-region">
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<ul class="sg-delete-confirmation-pattern__list" aria-label="Listen">
<li class="sg-delete-confirmation-pattern__list-item">
<button class="sg-interaction-element sg-button sg-delete-confirmation-pattern__list-button" type="button" data-overlay-list-toggle data-overlay-list-id="1" aria-pressed="true">
<span class="sg-delete-confirmation-pattern__list-icon" aria-hidden="true"></span>
<span class="sg-delete-confirmation-pattern__list-label">Liste 1</span>
</button>
</li>
<li class="sg-delete-confirmation-pattern__list-item">
<button class="sg-interaction-element sg-button sg-delete-confirmation-pattern__list-button" type="button" data-overlay-list-toggle data-overlay-list-id="2" aria-pressed="true">
<span class="sg-delete-confirmation-pattern__list-icon" aria-hidden="true"></span>
<span class="sg-delete-confirmation-pattern__list-label">Liste 2</span>
</button>
</li>
<li class="sg-delete-confirmation-pattern__list-item">
<button class="sg-interaction-element sg-button sg-delete-confirmation-pattern__list-button" type="button" data-overlay-list-toggle data-overlay-list-id="3" aria-pressed="false">
<span class="sg-delete-confirmation-pattern__list-icon" aria-hidden="true"></span>
<span class="sg-delete-confirmation-pattern__list-label">Liste 3</span>
</button>
</li>
<li class="sg-delete-confirmation-pattern__list-item">
<button class="sg-interaction-element sg-button sg-delete-confirmation-pattern__list-button" type="button" data-overlay-list-toggle data-overlay-list-id="4" aria-pressed="false">
<span class="sg-delete-confirmation-pattern__list-icon" aria-hidden="true"></span>
<span class="sg-delete-confirmation-pattern__list-label">Liste 4</span>
</button>
</li>
<li class="sg-delete-confirmation-pattern__list-item">
<button class="sg-interaction-element sg-button sg-delete-confirmation-pattern__list-button" type="button" data-overlay-list-toggle data-overlay-list-id="5" aria-pressed="false">
<span class="sg-delete-confirmation-pattern__list-icon" aria-hidden="true"></span>
<span class="sg-delete-confirmation-pattern__list-label">Liste 5</span>
</button>
</li>
</ul>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-delete-confirmation-pattern__create-list-toggle" type="button" data-create-list-form-toggle aria-expanded="false">Neue Liste anlegen</button>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body sg-delete-confirmation-pattern__create-list-segment" data-create-list-form hidden>
<p class="sg-body sg-delete-confirmation-pattern__text sg-delete-confirmation-pattern__create-list-title" data-create-list-title>Füge das Unternehmen einer neuen Liste hinzu</p>
<div class="sg-form-sections-card-wrapper sg-delete-confirmation-pattern__create-list-form" aria-label="Formular mit Abschnitten">
<form class="sg-form-sections-card" action="#" method="post" aria-label="Neue Liste anlegen Formular">
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<section class="sg-form-sections-card__chapter" aria-label="Neue Liste">
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Name</span>
<span class="sg-input-validation-stack">
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
placeholder="Name eingeben"
aria-label="Name"
maxlength="80"
aria-describedby="create-list-name-error"
>
<span class="sg-form-validation-text" id="create-list-name-error" hidden>Eine Liste mit diesem Namen existiert bereits.</span>
</span>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Beschreibung</span>
<textarea
class="sg-input-multi-line sg-form-inactive-selectable"
rows="4"
placeholder="Beschreibung eingeben"
aria-label="Beschreibung"
maxlength="350"
></textarea>
</label>
</div>
</section>
</div>
</form>
</div>
</div>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__actions-segment">
<div class="sg-delete-confirmation-pattern__actions sg-delete-confirmation-pattern__actions--footer">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-cancel>Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process" type="button" data-overlay-dialog-save>Speichern</button>
</div>
</footer>
</article>
</div>
</section>
<script>
const setupOverlayStage = (stage) => {
const confirmationInput = stage.querySelector('[data-overlay-confirmation-input]');
const confirmationSubmitButton = stage.querySelector('[data-overlay-confirmation-submit]');
const expectedConfirmationValue = stage.dataset.overlayConfirmationValue ?? '';
const overlayListButtons = Array.from(stage.querySelectorAll('[data-overlay-list-toggle]'));
const overlayList = stage.querySelector('.sg-delete-confirmation-pattern__list');
const createListForm = stage.querySelector('[data-create-list-form]');
const createListFormToggle = stage.querySelector('[data-create-list-form-toggle]');
const createListTitle = stage.querySelector('[data-create-list-title]');
const createListSegment = stage.querySelector('[data-create-list-form]');
const createListNameInput = stage.querySelector('[data-create-list-form] input[aria-label="Name"]');
const createListNameError = stage.querySelector('#create-list-name-error');
const createListDescriptionInput = stage.querySelector('[data-create-list-form] textarea[aria-label="Beschreibung"]');
const isAddToListOverlay = stage.dataset.pattern === 'add-to-list-overlay';
const committedListIds = new Set(
(stage.dataset.overlaySelectedLists ?? '')
.split(',')
.map((value) => value.trim())
.filter(Boolean)
);
let draftListIds = new Set(committedListIds);
let resetAddToListDraftState = () => {};
const updateFloatingCardHeight = (dialog) => {
const stageHeight = stage.getBoundingClientRect().height;
const floatingCardHeight = Math.max(0, Math.round(stageHeight * 0.9));
dialog.style.setProperty('--layout-delete-confirmation-overlay-height', `${floatingCardHeight}px`);
};
const closeStageDialogs = ({ resetAddToListDraft = false } = {}) => {
stage.querySelectorAll('[data-overlay-dialog]').forEach((dialog) => {
dialog.hidden = true;
});
if (isAddToListOverlay && resetAddToListDraft) {
resetAddToListDraftState();
}
stage.dataset.dialogOpen = 'false';
};
const syncOverlayListState = () => {
overlayListButtons.forEach((button) => {
const listId = button.dataset.overlayListId;
const isSelected = Boolean(listId && draftListIds.has(listId));
button.dataset.selected = String(isSelected);
button.setAttribute('aria-pressed', String(isSelected));
});
};
const bindOverlayListButton = (button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
const listId = button.dataset.overlayListId;
if (!listId) {
return;
}
if (draftListIds.has(listId)) {
draftListIds.delete(listId);
} else {
draftListIds.add(listId);
}
syncOverlayListState();
});
};
const appendCreatedListButton = (title) => {
if (!overlayList) {
return null;
}
const listId = `created-${Date.now()}`;
const listItem = document.createElement('li');
listItem.className = 'sg-delete-confirmation-pattern__list-item';
const button = document.createElement('button');
button.type = 'button';
button.className = 'sg-interaction-element sg-button sg-delete-confirmation-pattern__list-button';
button.dataset.overlayListToggle = '';
button.dataset.overlayListId = listId;
button.dataset.selected = 'true';
button.setAttribute('aria-pressed', 'true');
const icon = document.createElement('span');
icon.className = 'sg-delete-confirmation-pattern__list-icon';
icon.setAttribute('aria-hidden', 'true');
const label = document.createElement('span');
label.className = 'sg-delete-confirmation-pattern__list-label';
label.textContent = title;
button.append(icon, label);
listItem.appendChild(button);
overlayList.appendChild(listItem);
overlayListButtons.push(button);
bindOverlayListButton(button);
return listId;
};
const setCreateListNameErrorState = (isInvalid) => {
if (!createListNameInput || !createListNameError) {
return;
}
createListNameInput.setAttribute('aria-invalid', String(isInvalid));
createListNameError.hidden = !isInvalid;
if (!isInvalid) {
createListNameInput.removeAttribute('aria-describedby');
} else {
createListNameInput.setAttribute('aria-describedby', 'create-list-name-error');
}
};
if (confirmationInput && confirmationSubmitButton) {
const updateConfirmationState = () => {
const isValid = confirmationInput.value === expectedConfirmationValue;
confirmationSubmitButton.disabled = !isValid;
confirmationSubmitButton.setAttribute('aria-disabled', String(!isValid));
confirmationSubmitButton.classList.toggle('sg-button--process-inactive', !isValid);
};
confirmationInput.addEventListener('input', updateConfirmationState);
updateConfirmationState();
}
if (overlayListButtons.length > 0) {
syncOverlayListState();
}
if (createListForm && createListFormToggle) {
resetAddToListDraftState = () => {
draftListIds = new Set(committedListIds);
syncOverlayListState();
setCreateListNameErrorState(false);
if (createListSegment) {
createListSegment.hidden = true;
}
if (createListFormToggle) {
createListFormToggle.disabled = false;
createListFormToggle.setAttribute('aria-disabled', 'false');
createListFormToggle.setAttribute('aria-expanded', 'false');
createListFormToggle.classList.remove('sg-button--inactive');
createListFormToggle.classList.add('sg-button--active');
}
if (createListNameInput) {
createListNameInput.value = '';
}
if (createListDescriptionInput) {
createListDescriptionInput.value = '';
}
};
createListFormToggle.addEventListener('click', (event) => {
event.preventDefault();
setCreateListNameErrorState(false);
if (createListSegment) {
createListSegment.hidden = false;
}
createListFormToggle.setAttribute('aria-expanded', 'true');
createListFormToggle.classList.remove('sg-button--active');
createListFormToggle.classList.add('sg-button--inactive');
createListFormToggle.disabled = true;
createListFormToggle.setAttribute('aria-disabled', 'true');
});
}
if (isAddToListOverlay) {
resetAddToListDraftState();
}
closeStageDialogs({ resetAddToListDraft: false });
stage.querySelectorAll('[data-overlay-open-dialog]').forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const target = link.getAttribute('data-overlay-open-dialog');
const dialog = stage.querySelector(`[data-overlay-dialog="${target}"]`);
if (!dialog) {
return;
}
closeStageDialogs({ resetAddToListDraft: target === 'add-to-list' });
dialog.hidden = false;
updateFloatingCardHeight(dialog);
stage.dataset.dialogOpen = 'true';
const menuWrap = link.closest('.sg-sandwich-menu-wrap');
const menuButton = menuWrap?.querySelector('.sg-sandwich-button');
if (menuWrap) {
menuWrap.dataset.open = 'false';
}
if (menuButton) {
menuButton.setAttribute('aria-expanded', 'false');
}
});
});
stage.querySelectorAll('[data-overlay-dialog-close]').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
closeStageDialogs();
});
});
stage.querySelectorAll('[data-overlay-dialog-cancel]').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
closeStageDialogs({ resetAddToListDraft: true });
});
});
stage.querySelectorAll('[data-overlay-dialog-save]').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
if (isAddToListOverlay) {
const createdListTitle = createListNameInput?.value.trim() ?? '';
const existingListNames = new Set(
Array.from(stage.querySelectorAll('.sg-delete-confirmation-pattern__list-label'))
.map((label) => label.textContent?.trim().toLowerCase())
.filter(Boolean)
);
if (!createdListTitle) {
setCreateListNameErrorState(true);
return;
}
if (existingListNames.has(createdListTitle.toLowerCase())) {
setCreateListNameErrorState(true);
return;
}
setCreateListNameErrorState(false);
if (createdListTitle) {
const createdListId = appendCreatedListButton(createdListTitle);
if (createdListId) {
draftListIds.add(createdListId);
committedListIds.add(createdListId);
}
}
committedListIds.clear();
draftListIds.forEach((value) => committedListIds.add(value));
stage.dataset.overlaySelectedLists = Array.from(committedListIds).join(',');
}
closeStageDialogs({ resetAddToListDraft: true });
});
});
stage.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
const panel = wrap.querySelector('.sg-sandwich-menu-panel');
if (!button) {
return;
}
if (button.hasAttribute('data-overlay-open-dialog')) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
if (!nextState || !panel) {
return;
}
wrap.dataset.align = 'right';
const panelRect = panel.getBoundingClientRect();
if (panelRect.left < 0) {
wrap.dataset.align = 'left';
}
});
});
return closeStageDialogs;
};
const overlayStages = Array.from(document.querySelectorAll('.sg-delete-confirmation-pattern__stage'));
overlayStages.forEach((stage) => {
setupOverlayStage(stage);
});
document.addEventListener('click', (event) => {
if (event.target.closest('.sg-sandwich-menu-wrap')) {
return;
}
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,177 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Formular mit Abschnitten</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Formular mit Abschnitten</h1>
<section id="pattern-formular-mit-abschnitten">
<p class="sg-preview-label">Pattern: Formular mit Abschnitten</p>
<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Formular mit Abschnitten">
<form class="sg-form-sections-card" action="#" method="post">
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<h2 class="sg-strong sg-form-sections-card__title">Formular</h2>
<section class="sg-form-sections-card__chapter" aria-labelledby="form-kapitel-1">
<h2 id="form-kapitel-1" class="sg-strong sg-form-sections-card__chapter-title">Persoenliche Auswahl</h2>
<p class="sg-body sg-form-sections-card__sentence">Bitte waehlen Sie Ihre bevorzugte Kontaktart fuer die Rueckmeldung.</p>
<div class="sg-form-sections-card__option-group" role="radiogroup" aria-label="Kontaktart">
<label class="sg-checkbox-field-option sg-body" data-component="radio-field" data-component-state="inactive-selectable">
<button class="sg-radio-field sg-radio-field--inactive-selectable" type="button" role="radio" aria-checked="false" aria-label="Kontakt per E-Mail">
<span class="sg-radio-field__mark" aria-hidden="true"></span>
</button>
<span>E-Mail</span>
</label>
<label class="sg-checkbox-field-option sg-body" data-component="radio-field" data-component-state="inactive-selectable">
<button class="sg-radio-field sg-radio-field--inactive-selectable" type="button" role="radio" aria-checked="false" aria-label="Kontakt per Telefon">
<span class="sg-radio-field__mark" aria-hidden="true"></span>
</button>
<span>Telefon</span>
</label>
</div>
</section>
<section class="sg-form-sections-card__chapter" aria-labelledby="form-kapitel-2">
<h2 id="form-kapitel-2" class="sg-strong sg-form-sections-card__chapter-title">Optionale Angaben</h2>
<p class="sg-body sg-form-sections-card__sentence">Bitte markieren Sie die zusaetzlichen Informationen, die wir beruecksichtigen sollen.</p>
<div class="sg-form-sections-card__option-group" aria-label="Zusatzoptionen">
<label class="sg-checkbox-field-option sg-body" data-component="checkbox-field" data-component-state="inactive-selectable">
<button class="sg-checkbox-field sg-checkbox-field--inactive-selectable" type="button" role="checkbox" aria-checked="false" aria-label="Newsletter abonnieren">
<span class="sg-checkbox-field__mark" aria-hidden="true"></span>
</button>
<span>Newsletter abonnieren</span>
</label>
<label class="sg-checkbox-field-option sg-body" data-component="checkbox-field" data-component-state="inactive-selectable">
<button class="sg-checkbox-field sg-checkbox-field--inactive-selectable" type="button" role="checkbox" aria-checked="false" aria-label="Rueckruf am Vormittag erlauben">
<span class="sg-checkbox-field__mark" aria-hidden="true"></span>
</button>
<span>Rueckruf am Vormittag erlauben</span>
</label>
</div>
</section>
<section class="sg-form-sections-card__chapter" aria-labelledby="form-kapitel-3">
<h2 id="form-kapitel-3" class="sg-strong sg-form-sections-card__chapter-title">Freitext und Details</h2>
<p class="sg-body sg-form-sections-card__sentence">Bitte erfassen Sie einen kurzen Betreff und eine genauere Beschreibung Ihres Anliegens.</p>
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Betreff</span>
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
placeholder="Betreff eingeben"
aria-label="Betreff"
>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Nachricht</span>
<textarea
class="sg-input-multi-line sg-form-inactive-selectable"
rows="3"
placeholder="Nachricht eingeben"
aria-label="Nachricht"
></textarea>
</label>
</div>
</section>
</div>
<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">
<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">
<button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button">Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-form-sections-card__action" type="submit" disabled aria-disabled="true">Prozess Button</button>
</div>
</footer>
</form>
</div>
</section>
<script>
(() => {
const formCard = document.querySelector('.sg-form-sections-card');
if (!formCard) {
return;
}
const processButton = formCard.querySelector('.sg-button--process');
if (!processButton) {
return;
}
const updateProcessButtonState = () => {
const hasCheckedOption = Array.from(formCard.querySelectorAll('[role="radio"], [role="checkbox"]'))
.some((field) => field.getAttribute('aria-checked') === 'true');
const hasTextInput = Array.from(formCard.querySelectorAll('input[type="text"], textarea'))
.some((field) => field.value.trim().length > 0);
const isActive = hasCheckedOption || hasTextInput;
processButton.disabled = !isActive;
processButton.setAttribute('aria-disabled', String(!isActive));
processButton.classList.toggle('sg-button--process-inactive', !isActive);
};
formCard.querySelectorAll('[role="radiogroup"]').forEach((group) => {
group.querySelectorAll('[data-component="radio-field"]').forEach((option) => {
option.addEventListener('click', () => {
const selectedRadio = option.querySelector('.sg-radio-field');
if (!selectedRadio || selectedRadio.disabled) {
return;
}
group.querySelectorAll('[data-component="radio-field"]').forEach((otherOption) => {
const otherRadio = otherOption.querySelector('.sg-radio-field');
if (!otherRadio) {
return;
}
const isSelected = otherRadio === selectedRadio;
otherRadio.setAttribute('aria-checked', String(isSelected));
otherRadio.classList.toggle('sg-form-active', isSelected);
otherRadio.classList.toggle('sg-radio-field--inactive-selectable', !isSelected);
otherOption.setAttribute('data-component-state', isSelected ? 'active' : 'inactive-selectable');
});
updateProcessButtonState();
});
});
});
formCard.querySelectorAll('[data-component="checkbox-field"]').forEach((option) => {
option.addEventListener('click', () => {
const checkbox = option.querySelector('.sg-checkbox-field');
if (!checkbox || checkbox.disabled) {
return;
}
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
checkbox.classList.toggle('sg-form-active', nextState);
checkbox.classList.toggle('sg-checkbox-field--inactive-selectable', !nextState);
option.setAttribute('data-component-state', nextState ? 'active' : 'inactive-selectable');
updateProcessButtonState();
});
});
formCard.querySelectorAll('input[type="text"], textarea').forEach((field) => {
field.addEventListener('input', updateProcessButtonState);
});
updateProcessButtonState();
})();
</script>
</body>
</html>
@@ -0,0 +1,160 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Left Navigation</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Left Navigation</h1>
<section id="pattern-left-navigation" class="sg-left-navigation-pattern">
<p class="sg-preview-label">Pattern: Left Navigation</p>
<p class="sg-body">
Die linke Navigationsspalte nutzt auf Desktop 15% der verfügbaren Breite. Die zweite Group Card ist leer und dient in der Demo nur dazu, den restlichen Bildschirm zu füllen.
</p>
<div class="sg-left-navigation-pattern__layout" aria-label="Left Navigation Demo">
<aside class="sg-group-card sg-left-navigation-pattern__group-card sg-left-navigation-pattern__group-card--navigation" data-component="group-card" aria-label="Linke Navigation">
<div class="sg-group-card__header-row sg-left-navigation-pattern__header-row">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading">Navigation</h2>
<button
class="sg-interaction-element sg-button sg-button--active sg-left-navigation-pattern__toggle"
type="button"
data-left-navigation-toggle
aria-expanded="true"
aria-controls="left-navigation-menu"
>
Ausblenden
</button>
</div>
<nav class="sg-tab-button-group" id="left-navigation-menu" role="tablist" aria-label="Linksmenue Items" data-component="tab-navigation" data-component-size="large" data-component-variant="linksmenu-items">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="true" data-component-part="tab-button">Übersicht</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button">Unternehmen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button">Kennzahlen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button">Nachrichten</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button">Einstellungen</button>
</nav>
</aside>
<section class="sg-group-card sg-left-navigation-pattern__group-card sg-left-navigation-pattern__group-card--content" data-component="group-card">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading" data-left-navigation-content-heading>Übersicht</h2>
<p class="sg-body sg-left-navigation-pattern__content-text">
Zeile 1: Überblick über die aktuelle Navigation.<br>
Zeile 2: Die Inhalte orientieren sich am aktiven Menüpunkt.<br>
Zeile 3: Jede Auswahl aktualisiert die Überschrift.<br>
Zeile 4: Die Content-Card bleibt optisch eigenständig.<br>
Zeile 5: Der Aufbau folgt dem linken Navigationsbereich.<br>
Zeile 6: Die Breite bleibt flexibel innerhalb des Layouts.<br>
Zeile 7: Lange Inhalte werden im Card-Rahmen geführt.<br>
Zeile 8: Der Text dient als Platzhalter für spätere Inhalte.<br>
Zeile 9: Die Darstellung bleibt auf Desktop und Mobile stabil.<br>
Zeile 10: Die Navigation links steuert den sichtbaren Zustand.<br>
Zeile 11: Der aktive Punkt ist visuell hervorgehoben.<br>
Zeile 12: Inaktive Punkte bleiben als klare Auswahloptionen sichtbar.<br>
Zeile 13: Die Content-Card reagiert auf den ausgewählten Eintrag.<br>
Zeile 14: Die Überschrift übernimmt den Namen des Menüpunkts.<br>
Zeile 15: Der Text kann später durch Fachinhalte ersetzt werden.<br>
Zeile 16: Die Gruppe bleibt als zusammenhängender Bereich lesbar.<br>
Zeile 17: Kartenabstände folgen den vorhandenen Styleguide-Tokens.<br>
Zeile 18: Keine lokalen Sonderregeln sind für den Demo-Text nötig.<br>
Zeile 19: Die Struktur bleibt bewusst einfach und nachvollziehbar.<br>
Zeile 20: Das Layout zeigt Navigation und Inhalt nebeneinander.<br>
Zeile 21: Der linke Bereich bleibt auf die Navigation fokussiert.<br>
Zeile 22: Der rechte Bereich zeigt den zugehörigen Inhalt.<br>
Zeile 23: Der Text dient als Füllmaterial für die Layoutprüfung.<br>
Zeile 24: Die Card-Höhe passt sich dem vorhandenen Inhalt an.<br>
Zeile 25: Scrollverhalten ist in diesem Demo-Zustand nicht nötig.<br>
Zeile 26: Die Darstellung bleibt ohne zusätzliche Interaktion verständlich.<br>
Zeile 27: Weitere Inhalte lassen sich später ergänzen oder ersetzen.<br>
Zeile 28: Die Auswahl folgt weiterhin dem Tab-Button-Verhalten.<br>
Zeile 29: Visuelle Konsistenz hat Vorrang vor dekorativen Effekten.<br>
Zeile 30: Der Demo-Text bleibt neutral und fachlich unaufgeladen.<br>
Zeile 31: Jede Zeile stützt die Prüfung der vertikalen Kartengröße.<br>
Zeile 32: Der Content-Bereich ist damit klar als gefüllte Fläche erkennbar.<br>
Zeile 33: Der Text nutzt die vorhandenen Typografie-Tokens.<br>
Zeile 34: Die Lesbarkeit bleibt auf hellem Inhaltshintergrund hoch.<br>
Zeile 35: Die Spaltenlogik wird durch den Text noch deutlicher.<br>
Zeile 36: Das Beispiel unterstützt die Wahrnehmung des Pattern-Rahmens.<br>
Zeile 37: Der Inhalt bleibt austauschbar und ohne Geschäftslogik.<br>
Zeile 38: Die Group Card dient als Container für den Demo-Inhalt.<br>
Zeile 39: Das Muster zeigt die vorgesehene Struktur des Portals.<br>
Zeile 40: Die finale Zeile schließt den Demo-Block sauber ab.
</p>
</section>
</div>
</section>
<script>
(() => {
const mediaQuery = window.matchMedia('(max-width: 767px)');
const toggle = document.querySelector('[data-left-navigation-toggle]');
const menu = document.getElementById('left-navigation-menu');
const contentHeading = document.querySelector('[data-left-navigation-content-heading]');
if (!toggle || !menu) {
return;
}
const setMenuState = (expanded) => {
menu.hidden = !expanded;
menu.classList.toggle('sg-left-navigation-pattern__menu--collapsed', !expanded);
toggle.setAttribute('aria-expanded', String(expanded));
toggle.textContent = expanded ? 'Ausblenden' : 'Einblenden';
toggle.classList.add('sg-button--active');
toggle.classList.remove('sg-button--inactive');
};
const setContentHeading = (button) => {
if (!contentHeading || !button) {
return;
}
contentHeading.textContent = button.textContent.trim();
};
const syncMode = () => {
if (mediaQuery.matches) {
if (toggle.getAttribute('aria-expanded') !== 'true' && toggle.getAttribute('aria-expanded') !== 'false') {
setMenuState(true);
return;
}
setMenuState(toggle.getAttribute('aria-expanded') !== 'false');
return;
}
menu.hidden = false;
menu.classList.remove('sg-left-navigation-pattern__menu--collapsed');
toggle.setAttribute('aria-expanded', 'true');
toggle.textContent = 'Ausblenden';
toggle.classList.add('sg-button--active');
toggle.classList.remove('sg-button--inactive');
};
toggle.addEventListener('click', () => {
setMenuState(menu.hidden);
});
menu.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
menu.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
otherButton.setAttribute('aria-selected', String(otherButton === button));
});
setContentHeading(button);
});
});
setContentHeading(menu.querySelector('.sg-tab-button[aria-selected="true"]'));
syncMode();
mediaQuery.addEventListener('change', syncMode);
})();
</script>
</body>
</html>
@@ -0,0 +1,602 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Multiselektions-Pulldown</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Multiselektions-Pulldown</h1>
<section id="pattern-multiselektions-pulldown">
<p class="sg-preview-label">Pattern: Multiselektions-Pulldown</p>
<div class="sg-options-row" aria-label="Multiselektions-Pulldown Wrapper" data-pattern="multiselektions-pulldown">
<div class="sg-options-row__left" data-pattern-part="multiselektions-pulldown-trigger-area">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="multiple" data-component="pulldown" data-component-state="active" data-force-active="true">
<span class="sg-activatable-control">
<button class="sg-interaction-element sg-pulldown sg-pulldown--selected sg-form-active sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Multiselektions-Pulldown mit aktiver Auswahl" data-label-base="Multiselektions-Pulldown" data-component-part="pulldown-trigger">
Multiselektions-Pulldown
</button>
</span>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Multiselektions-Pulldown" data-component-part="pulldown-panel">
<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Multiselektions-Inhalt">
<form class="sg-form-sections-card" action="#" method="post">
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<section class="sg-form-sections-card__chapter" aria-labelledby="multiselect-block-1">
<h2 id="multiselect-block-1" class="sg-strong sg-form-sections-card__chapter-title">Block 1</h2>
<label class="sg-checkbox-field-option sg-body" data-component="checkbox-field" data-component-state="inactive-selectable">
<button class="sg-checkbox-field sg-checkbox-field--inactive-selectable" type="button" role="checkbox" aria-checked="false" aria-label="Checkbox 1 wählen" data-pulldown-option>
<span class="sg-checkbox-field__mark" aria-hidden="true"></span>
</button>
<span>Checkbox 1</span>
</label>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 1: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-1-label">Slider 1</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="6.5"
aria-labelledby="multiselect-slider-1-label"
disabled
>
<output class="sg-slider-value" for="slider">6.5</output>
</div>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 2: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-2-label">Slider 2</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="9.5"
aria-labelledby="multiselect-slider-2-label"
disabled
>
<output class="sg-slider-value" for="slider">9.5</output>
</div>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 3: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-3-label">Slider 3</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="5.0"
aria-labelledby="multiselect-slider-3-label"
disabled
>
<output class="sg-slider-value" for="slider">5.0</output>
</div>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 4: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-4-label">Slider 4</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="6.5"
aria-labelledby="multiselect-slider-4-label"
disabled
>
<output class="sg-slider-value" for="slider">6.5</output>
</div>
</section>
<section class="sg-form-sections-card__chapter" aria-labelledby="multiselect-block-2">
<h2 id="multiselect-block-2" class="sg-strong sg-form-sections-card__chapter-title">Block 2</h2>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 5: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-5-label">Slider 5</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="3.0"
aria-labelledby="multiselect-slider-5-label"
disabled
>
<output class="sg-slider-value" for="slider">3.0</output>
</div>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 6: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-6-label">Slider 6</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="2.5"
aria-labelledby="multiselect-slider-6-label"
disabled
>
<output class="sg-slider-value" for="slider">2.5</output>
</div>
<div class="sg-slider-row sg-slider-row--inactive-selectable" data-component="slider" data-component-state="inactive-selectable" data-activatable="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Slider 7: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-slider-7-label">Slider 7</span>
<input
class="sg-interaction-element sg-slider sg-form-inactive-selectable"
type="range"
min="1"
max="10"
step="0.1"
value="1.6"
aria-labelledby="multiselect-slider-7-label"
disabled
>
<output class="sg-slider-value" for="slider">1.6</output>
</div>
</section>
<section class="sg-form-sections-card__chapter" aria-labelledby="multiselect-block-3">
<h2 id="multiselect-block-3" class="sg-strong sg-form-sections-card__chapter-title">Block 3</h2>
<div id="component-radio-activatable" class="sg-checkbox-field-option sg-checkbox-field-option--inactive-selectable sg-body sg-radio-activatable-group" data-component="radio-field" data-component-state="inactive-selectable" data-activatable="true" data-activatable-radio-group="true">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Radio Auswahl: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<span class="sg-label" id="multiselect-radio-group-label">Radio Auswahl</span>
<span class="sg-radio-activatable-group__choices">
<span class="sg-radio-activatable-group__choice">
<button class="sg-radio-field sg-radio-field--inactive-selectable" type="button" role="radio" aria-checked="false" aria-label="Radio 1 wählen" aria-describedby="multiselect-radio-group-label">
<span class="sg-radio-field__mark" aria-hidden="true"></span>
</button>
<span>Radio 1</span>
</span>
<span class="sg-radio-activatable-group__choice">
<button class="sg-radio-field sg-radio-field--inactive-selectable" type="button" role="radio" aria-checked="false" aria-label="Radio 2 wählen" aria-describedby="multiselect-radio-group-label">
<span class="sg-radio-field__mark" aria-hidden="true"></span>
</button>
<span>Radio 2</span>
</span>
</span>
</div>
<div class="sg-pulldown-panel__row sg-pulldown-panel__row--disabled" data-pulldown-filter-row data-active="false" data-component-part="pulldown-filter-row">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Pulldown 1: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<p class="sg-pulldown-panel__label sg-body">Pulldown 1</p>
<select class="sg-interaction-element sg-pulldown" aria-label="Pulldown 1 Auswahl" disabled>
<option selected>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
</section>
<section class="sg-form-sections-card__chapter" aria-labelledby="multiselect-block-4">
<h2 id="multiselect-block-4" class="sg-strong sg-form-sections-card__chapter-title">Block 4</h2>
<div class="sg-pulldown-panel__row sg-pulldown-panel__row--disabled" data-pulldown-filter-row data-active="false" data-component-part="pulldown-filter-row">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Pulldown 2: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<p class="sg-pulldown-panel__label sg-body">Pulldown 2</p>
<select class="sg-interaction-element sg-pulldown" aria-label="Pulldown 2 Auswahl" disabled>
<option selected>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
<div class="sg-pulldown-panel__row sg-pulldown-panel__row--disabled" data-pulldown-filter-row data-active="false" data-component-part="pulldown-filter-row">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Pulldown 3: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<p class="sg-pulldown-panel__label sg-body">Pulldown 3</p>
<select class="sg-interaction-element sg-pulldown" aria-label="Pulldown 3 Auswahl" disabled>
<option selected>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
<div class="sg-pulldown-panel__row sg-pulldown-panel__row--disabled" data-pulldown-filter-row data-active="false" data-component-part="pulldown-filter-row">
<button class="sg-mode-toggle sg-mode-toggle--local sg-activation-mode-toggle" type="button" data-active="absolute" aria-label="Aktivierung Pulldown 4: aus">
<span class="sg-mode-toggle__switch" aria-hidden="true">
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--left">aus</span>
<span class="sg-activation-mode-toggle__switch-label sg-activation-mode-toggle__switch-label--right">an</span>
<span class="sg-mode-toggle__handle"></span>
</span>
</button>
<p class="sg-pulldown-panel__label sg-body">Pulldown 4</p>
<select class="sg-interaction-element sg-pulldown" aria-label="Pulldown 4 Auswahl" disabled>
<option selected>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
</section>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<script>
(() => {
const setLocalActivationState = (toggle, isActive) => {
const sliderRow = toggle.closest('.sg-slider-row[data-activatable="true"]');
if (sliderRow) {
const slider = sliderRow.querySelector('.sg-slider');
if (!slider) {
return;
}
sliderRow.dataset.componentState = isActive ? 'active' : 'inactive-selectable';
sliderRow.classList.toggle('sg-slider-row--inactive-selectable', !isActive);
slider.classList.toggle('sg-form-active', isActive);
slider.classList.toggle('sg-form-inactive-selectable', !isActive);
slider.disabled = !isActive;
return;
}
const radioGroup = toggle.closest('[data-activatable-radio-group="true"]');
if (radioGroup) {
const radios = radioGroup.querySelectorAll('.sg-radio-field');
radioGroup.dataset.componentState = isActive ? 'active' : 'inactive-selectable';
radios.forEach((radio) => {
radio.disabled = !isActive;
if (!isActive) {
radio.setAttribute('aria-checked', 'false');
}
const checked = radio.getAttribute('aria-checked') === 'true';
radio.classList.toggle('sg-form-active', checked);
radio.classList.toggle('sg-radio-field--inactive-selectable', !isActive);
});
return;
}
const pulldownRow = toggle.closest('[data-pulldown-filter-row]');
if (pulldownRow) {
const select = pulldownRow.querySelector('.sg-pulldown');
if (!select) {
return;
}
pulldownRow.dataset.active = isActive ? 'true' : 'false';
select.disabled = !isActive;
pulldownRow.classList.toggle('sg-pulldown-panel__row--disabled', !isActive);
select.classList.toggle('sg-pulldown--selected', isActive);
select.classList.toggle('sg-pulldown--inactive-selectable', !isActive);
}
};
document.querySelectorAll('.sg-activation-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = toggle.dataset.active === 'absolute' ? 'relative' : 'absolute';
const isActive = nextState === 'relative';
toggle.dataset.active = nextState;
toggle.setAttribute('aria-label', toggle.getAttribute('aria-label').replace(/(an|aus)$/, isActive ? 'an' : 'aus'));
setLocalActivationState(toggle, isActive);
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
const forceActive = demo.dataset.forceActive === 'true';
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = forceActive
? labelBase
: (selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase);
trigger.classList.toggle('sg-pulldown--selected', forceActive || selectedCount > 0);
trigger.classList.toggle('sg-form-active', forceActive || selectedCount > 0);
trigger.classList.toggle('sg-pulldown--inactive-selectable', !forceActive && selectedCount === 0);
trigger.setAttribute(
'aria-label',
forceActive || selectedCount > 0 ? `${labelBase} mit aktiver Auswahl` : `${labelBase} ohne aktive Auswahl`
);
};
const updateMultiselectLabelAlignment = () => {
document.querySelectorAll('.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-pulldown-panel').forEach((panel) => {
const labels = panel.querySelectorAll(
'.sg-slider-row[data-activatable="true"] > .sg-label, .sg-radio-activatable-group > .sg-label, [data-pulldown-filter-row] > .sg-pulldown-panel__label'
);
let maxWidth = 0;
labels.forEach((label) => {
const width = Math.ceil(label.scrollWidth || label.getBoundingClientRect().width);
if (width > maxWidth) {
maxWidth = width;
}
});
if (maxWidth > 0) {
panel.style.setProperty('--sg-multiselect-label-column-width', `${maxWidth}px`);
} else {
panel.style.removeProperty('--sg-multiselect-label-column-width');
}
});
};
document.querySelectorAll('.sg-slider-row').forEach((row) => {
const slider = row.querySelector('.sg-slider');
const valueOutput = row.querySelector('.sg-slider-value');
if (!slider || !valueOutput) {
return;
}
const updateSliderState = () => {
const min = Number(slider.min || 0);
const max = Number(slider.max || 100);
const value = Number(slider.value || 0);
const denominator = max - min;
const progress = denominator > 0 ? ((value - min) / denominator) * 100 : 0;
slider.style.setProperty('--sg-slider-progress', `${progress}%`);
valueOutput.textContent = value.toFixed(1);
};
slider.addEventListener('input', () => {
if (row.dataset.activatable === 'true' && row.dataset.componentState !== 'active') {
return;
}
updateSliderState();
});
updateSliderState();
});
document.querySelectorAll('.sg-checkbox-field').forEach((checkbox) => {
checkbox.addEventListener('click', (event) => {
event.stopPropagation();
if (checkbox.disabled) {
return;
}
const activatableOption = checkbox.closest('[data-component="checkbox-field"][data-activatable="true"]');
if (activatableOption) {
checkbox.setAttribute('aria-checked', 'true');
checkbox.classList.add('sg-form-active');
checkbox.classList.remove('sg-checkbox-field--inactive-selectable');
activatableOption.dataset.componentState = 'active';
}
const pulldownDemo = checkbox.closest('.sg-pulldown-demo');
if (pulldownDemo) {
updatePulldownSelectionState(pulldownDemo);
pulldownDemo.dataset.open = 'true';
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
}
});
});
document.querySelectorAll('.sg-radio-field').forEach((radio) => {
radio.addEventListener('click', (event) => {
event.stopPropagation();
if (radio.disabled) {
return;
}
const activatableGroup = radio.closest('[data-activatable-radio-group="true"]');
if (!activatableGroup) {
return;
}
if (activatableGroup.dataset.componentState !== 'active') {
return;
}
const radios = activatableGroup.querySelectorAll('.sg-radio-field');
radios.forEach((otherRadio) => {
otherRadio.setAttribute('aria-checked', String(otherRadio === radio));
otherRadio.disabled = false;
otherRadio.classList.remove('sg-radio-field--inactive-selectable');
otherRadio.classList.toggle('sg-form-active', otherRadio === radio);
});
});
});
document.querySelectorAll('[data-activatable-radio-group="true"]').forEach((group) => {
const isActive = group.dataset.componentState === 'active';
group.querySelectorAll('.sg-radio-field').forEach((radio) => {
radio.disabled = !isActive;
});
});
updateMultiselectLabelAlignment();
window.addEventListener('resize', updateMultiselectLabelAlignment);
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
if (demo.dataset.activatable === 'true') {
trigger.classList.add('sg-pulldown--selected', 'sg-form-active');
trigger.classList.remove('sg-pulldown--inactive-selectable');
}
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
updateMultiselectLabelAlignment();
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
demo.dataset.align = 'left';
panel.style.left = '0px';
panel.style.right = 'auto';
const initialRect = panel.getBoundingClientRect();
let offsetX = 0;
const maxRight = window.innerWidth;
if (initialRect.right > maxRight) {
offsetX -= initialRect.right - maxRight;
}
if (initialRect.left + offsetX < 0) {
offsetX += 0 - (initialRect.left + offsetX);
}
panel.style.left = `${offsetX}px`;
});
});
document.querySelectorAll('[data-pulldown-filter-row]').forEach((row) => {
const select = row.querySelector('.sg-pulldown');
if (!select) {
return;
}
const updateFilterRowState = () => {
const isActive = row.dataset.active === 'true';
row.classList.toggle('sg-pulldown-panel__row--disabled', !isActive);
select.classList.toggle('sg-pulldown--selected', isActive);
select.classList.toggle('sg-pulldown--inactive-selectable', !isActive);
select.disabled = !isActive;
};
updateFilterRowState();
});
document.addEventListener('click', (event) => {
if (event.target.closest('.sg-pulldown-demo')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
})();
</script>
</body>
</html>
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Navigation Card</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Navigation Card</h1>
<section id="pattern-navigation-card">
<p class="sg-preview-label">Pattern: Navigation Card</p>
<div class="sg-preview-area sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
</section>
</body>
</html>
+264
View File
@@ -0,0 +1,264 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Notifications</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Notifications</h1>
<section id="pattern-notifications">
<p class="sg-preview-label">Pattern: Notifications</p>
<div class="sg-preview-area sg-notifications-pattern">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
</div>
</section>
<section id="pattern-notifications-with-title">
<p class="sg-preview-label">Pattern: Notification with title</p>
<div class="sg-preview-area sg-notifications-pattern">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Veeva Systems Inc Class A</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Veeva Systems Inc Class A</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Veeva Systems Inc Class A</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Veeva Systems Inc Class A</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
</div>
</section>
<section id="pattern-notifications-small">
<p class="sg-preview-label">Pattern: Notifications small</p>
<div class="sg-preview-area sg-notifications-pattern">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-small" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment sg-notifications-pattern__text-segment--small" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-small" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__text-segment--small" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-small" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment sg-notifications-pattern__text-segment--small" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-small" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__text-segment--small" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">
zum Unternehmen
</button>
</div>
</article>
</div>
</section>
<script>
const updateNotificationsPatternRowState = () => {
document.querySelectorAll('.sg-notifications-pattern').forEach((grid) => {
const cards = Array.from(grid.querySelectorAll('.sg-notifications-pattern__card'));
grid.classList.remove('sg-notifications-pattern--multi-row');
grid.style.removeProperty('--layout-object-card-shared-width');
if (cards.length <= 1) {
return;
}
const firstTop = cards[0].offsetTop;
const hasMultipleRows = cards.some((card) => card.offsetTop !== firstTop);
if (!hasMultipleRows) {
return;
}
const firstRowCards = cards.filter((card) => card.offsetTop === firstTop);
const referenceCard = firstRowCards[0];
if (!referenceCard) {
return;
}
const referenceWidth = referenceCard.getBoundingClientRect().width;
grid.style.setProperty('--layout-object-card-shared-width', `${referenceWidth}px`);
grid.classList.add('sg-notifications-pattern--multi-row');
});
};
window.addEventListener('load', updateNotificationsPatternRowState);
window.addEventListener('resize', updateNotificationsPatternRowState);
</script>
</body>
</html>
+250
View File
@@ -0,0 +1,250 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Object Card</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Object Card</h1>
<section id="pattern-object-card">
<p class="sg-preview-label">Pattern: Object Card</p>
<p class="sg-body">Hinweis: Die hier gezeigten sechs Karten sind nur eine illustrative Demo zur Prüfung des responsiven Verhaltens. Für das Pattern selbst ist die Anzahl der Karten nicht vorgegeben und hängt vom konkreten Use Case ab.</p>
<div class="sg-object-card-grid">
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Alcon Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
</div>
<h2 class="sg-sub-heading sg-section-h2">Object Card Content Basic</h2>
<p class="sg-preview-label">Pattern: Object Card Content Basic</p>
<p class="sg-body">Hinweis: Diese Variante ist für spezifische Inhalte gedacht, die eine Nutzerinteraktion erfordern, zum Beispiel Zustimmungen oder Freigaben. Im Styleguide wird hier nur das Card-Layout gezeigt.</p>
<article class="sg-card sg-object-card sg-object-card--content-basic sg-object-card-content-basic" data-pattern="object-card-content-basic" aria-label="Objekt Card Content Basic">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-content-basic-header">
<div class="sg-strong">Zustimmung zu Geschäftsbedingungen</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content-basic-content">
<p class="sg-body">Bevor Sie fortfahren, prüfen Sie bitte die Inhalte der Geschäftsbedingungen und bestätigen Sie die Zustimmung im Zielsystem.</p>
<p class="sg-body">Die Karte ist bewusst kompakt gehalten und für lesbare, interaktionspflichtige Inhalte auf Desktop mittig ausgerichtet.</p>
</div>
</article>
<h2 class="sg-sub-heading sg-section-h2">Object Card variable height</h2>
<p class="sg-preview-label">Pattern: Object Card variable height</p>
<p class="sg-body">Hinweis: In dieser Variante ist die komplette Karte in der Höhe flexibel. Die Segmenthöhen ergeben sich direkt aus dem Inhalt, dadurch sind die Karten im Grid bewusst unterschiedlich hoch.</p>
<div class="sg-object-card-grid">
<article class="sg-card sg-object-card sg-object-card--variable-height" data-pattern="object-card" aria-label="Objekt Card variable Höhe">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Alcon Inc.</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Kurzer Inhalt für eine kompakte Karte mit wenig vertikalem Platzbedarf.</p>
</div>
</article>
<article class="sg-card sg-object-card sg-object-card--variable-height" data-pattern="object-card" aria-label="Objekt Card variable Höhe">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Meyer Optik AG</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Etwas längerer Text mit mehr Inhalt. Die Karte wächst hier sichtbar mit dem Segment, weil der Body bewusst mehr Umbruchzeilen erzeugt und damit die Höhe definiert.</p>
</div>
</article>
<article class="sg-card sg-object-card sg-object-card--variable-height" data-pattern="object-card" aria-label="Objekt Card variable Höhe">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Nordwind Therapeutics Holding</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Der Inhalt ist hier knapp, aber die Karte bleibt flexibel und nimmt nur so viel Höhe ein wie die Segmente tatsächlich benötigen.</p>
</div>
</article>
<article class="sg-card sg-object-card sg-object-card--variable-height" data-pattern="object-card" aria-label="Objekt Card variable Höhe">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Valencia Holding Group International</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Dies ist die längste Variante in der Demo. Sie zeigt, dass der Body die Kartenhöhe direkt mitprägt, ohne dass ein fixer Kartenrahmen die Inhalte begrenzt oder künstlich streckt.</p>
</div>
</article>
</div>
</section>
<script>
const updateObjectCardGridRowState = () => {
document.querySelectorAll('.sg-object-card-grid').forEach((grid) => {
const cards = Array.from(grid.querySelectorAll('.sg-object-card'));
grid.classList.remove('sg-object-card-grid--multi-row');
grid.style.removeProperty('--layout-object-card-shared-width');
if (cards.length <= 1) {
return;
}
// Measure in native responsive state first (no shared-width lock).
// Reading offsetTop after reset forces layout with current viewport width.
const firstTop = cards[0].offsetTop;
const hasMultipleRows = cards.some((card) => card.offsetTop !== firstTop);
if (!hasMultipleRows) {
return;
}
const firstRowCards = cards.filter((card) => card.offsetTop === firstTop);
const referenceCard = firstRowCards[0];
if (!referenceCard) {
return;
}
const referenceWidth = referenceCard.getBoundingClientRect().width;
grid.style.setProperty('--layout-object-card-shared-width', `${referenceWidth}px`);
grid.classList.add('sg-object-card-grid--multi-row');
});
};
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
const panel = wrap.querySelector('.sg-sandwich-menu-panel');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
if (!nextState || !panel) {
return;
}
wrap.dataset.align = 'right';
const panelRect = panel.getBoundingClientRect();
if (panelRect.left < 0) {
wrap.dataset.align = 'left';
}
});
});
document.addEventListener('click', (event) => {
if (event.target.closest('.sg-sandwich-menu-wrap')) {
return;
}
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
});
window.addEventListener('load', updateObjectCardGridRowState);
window.addEventListener('resize', updateObjectCardGridRowState);
</script>
</body>
</html>
@@ -0,0 +1,152 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Object Group Card</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Object Group Card</h1>
<section id="pattern-object-group-card">
<p class="sg-preview-label">Pattern: Object Group Card</p>
<p class="sg-body sg-object-group-card__hint">Hinweis: Die hier gezeigten sechs Karten sind nur eine illustrative Demo zur Prüfung des responsiven Verhaltens. Für das Pattern selbst ist die Anzahl der Karten nicht vorgegeben und hängt vom konkreten Use Case ab.</p>
<div class="sg-object-card-grid">
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Objekt Group Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header">
<div class="sg-strong">Alcon Inc.</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-group-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-group-card-actions">
<div class="sg-object-card__actions sg-object-group-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Zur Gruppe</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Objekt Group Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header"><div class="sg-strong">Alcon Inc.</div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-group-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-group-card-actions">
<div class="sg-object-card__actions sg-object-group-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Zur Gruppe</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Objekt Group Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header"><div class="sg-strong">Alcon Inc.</div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-group-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-group-card-actions">
<div class="sg-object-card__actions sg-object-group-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Zur Gruppe</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Objekt Group Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header"><div class="sg-strong">Alcon Inc.</div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-group-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-group-card-actions">
<div class="sg-object-card__actions sg-object-group-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Zur Gruppe</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Objekt Group Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header"><div class="sg-strong">Alcon Inc.</div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-group-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-group-card-actions">
<div class="sg-object-card__actions sg-object-group-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Zur Gruppe</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Objekt Group Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header"><div class="sg-strong">Alcon Inc.</div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-group-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-group-card-actions">
<div class="sg-object-card__actions sg-object-group-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Zur Gruppe</button>
</div>
</footer>
</article>
</div>
</section>
<section id="pattern-list-group-card-height-600">
<p class="sg-preview-label">Pattern: List Group Card</p>
<p class="sg-body sg-object-group-card__hint">Variante: List Group Card, identisch, nur die Höhe ist 600px.</p>
<div class="sg-object-card-grid sg-list-group-card--height-600" aria-label="List Group Card Höhe 600px"></div>
</section>
<script>
const populateListGroupCardVariant = () => {
const sourceGrid = document.querySelector('#pattern-object-group-card .sg-object-card-grid');
const variantGrid = document.querySelector('#pattern-list-group-card-height-600 .sg-object-card-grid');
if (!sourceGrid || !variantGrid || variantGrid.children.length > 0) {
return;
}
variantGrid.innerHTML = sourceGrid.innerHTML;
};
const updateObjectCardGridRowState = () => {
document.querySelectorAll('.sg-object-card-grid').forEach((grid) => {
const cards = Array.from(grid.querySelectorAll('.sg-object-card'));
grid.classList.remove('sg-object-card-grid--multi-row');
grid.style.removeProperty('--layout-object-card-shared-width');
if (cards.length <= 1) {
return;
}
// Measure in native responsive state first (no shared-width lock).
// Reading offsetTop after reset forces layout with current viewport width.
const firstTop = cards[0].offsetTop;
const hasMultipleRows = cards.some((card) => card.offsetTop !== firstTop);
if (!hasMultipleRows) {
return;
}
const firstRowCards = cards.filter((card) => card.offsetTop === firstTop);
const referenceCard = firstRowCards[0];
if (!referenceCard) {
return;
}
const referenceWidth = referenceCard.getBoundingClientRect().width;
const maxWidth = parseFloat(getComputedStyle(referenceCard).maxWidth);
const sharedWidth = Number.isFinite(maxWidth) ? Math.min(referenceWidth, maxWidth) : referenceWidth;
grid.style.setProperty('--layout-object-card-shared-width', `${sharedWidth}px`);
grid.classList.add('sg-object-card-grid--multi-row');
});
};
window.addEventListener('load', () => {
populateListGroupCardVariant();
updateObjectCardGridRowState();
});
window.addEventListener('resize', updateObjectCardGridRowState);
</script>
</body>
</html>
+382
View File
@@ -0,0 +1,382 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Options Row</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Options Row</h1>
<section id="pattern-options-row">
<p class="sg-preview-label">Pattern: Options Row</p>
<p class="sg-table-label" style="color: var(--text-options-row-description);">
Desktop/Tablet: gleiche Breite wie das zugehörige Element; linke Optionen linksbündig, rechte Optionen rechtsbündig.
</p>
<p class="sg-table-label" style="color: var(--text-options-row-description);">
Mobile: rechte Optionen werden zum oberen Segment, linke Optionen zum unteren Segment; linke Optionen brechen innerhalb der verfügbaren Breite um.
</p>
<div class="sg-options-row" aria-label="Optionszeile" data-pattern="options-row">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Sortierung" data-component-part="pulldown-trigger" data-label-base="Sortierung">
Sortierung
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Sortierung" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Sortierungsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Region" data-component-part="pulldown-trigger" data-label-base="Region">
Region
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Region" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Regionsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input
class="sg-interaction-element sg-input-single-line"
type="text"
placeholder="Suche"
aria-label="Suche"
>
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
<div class="sg-options-row__right" data-pattern-part="options-row-secondary-actions">
<button class="sg-mode-toggle" type="button" data-active="relative" aria-label="Modus Schieber global: relativ aktiv" data-component="mode-toggle" data-component-context="options-row">
<span class="sg-mode-toggle__label" data-component-part="toggle-label">absolut</span>
<span class="sg-mode-toggle__switch" aria-hidden="true" data-component-part="toggle-track">
<span class="sg-mode-toggle__handle" data-component-part="toggle-handle"></span>
</span>
<span class="sg-mode-toggle__label" data-component-part="toggle-label">relativ</span>
</button>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="options-row">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.
</span>
</span>
</div>
</div>
</section>
<section id="pattern-options-row-left-only">
<p class="sg-preview-label">Pattern: Options row left only</p>
<p class="sg-table-label" style="color: var(--text-options-row-description);">
Desktop/Tablet: nur linksbündige Elemente innerhalb einer gemeinsamen Options Row.
</p>
<p class="sg-table-label" style="color: var(--text-options-row-description);">
Mobile: keine zweite Segmentierung, die linke Gruppe bleibt als einziger Block erhalten.
</p>
<div class="sg-options-row sg-options-row--left-only" aria-label="Optionszeile nur links" data-pattern="options-row-left-only">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Kategorie" data-component-part="pulldown-trigger" data-label-base="Kategorie">
Kategorie
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Kategorie" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Kategorienoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input
class="sg-interaction-element sg-input-single-line"
type="text"
placeholder="Suche"
aria-label="Suche"
>
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
</div>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
// Pulldown demo logic:
// Pulldowns with checkbox options stay open while options are toggled,
// because users may select or deselect several options in one opened panel.
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const triggerRect = trigger.getBoundingClientRect();
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.width > triggerRect.width || panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-checkbox-field').forEach((checkbox) => {
checkbox.addEventListener('click', (event) => {
event.stopPropagation();
if (checkbox.disabled) {
return;
}
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
const pulldownDemo = checkbox.closest('.sg-pulldown-demo');
if (pulldownDemo) {
updatePulldownSelectionState(pulldownDemo);
pulldownDemo.dataset.open = 'true';
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
const selectionMode = pulldownDemo.dataset.selectionMode || 'single';
if (selectionMode === 'multiple') {
const nextState = option.getAttribute('aria-checked') !== 'true';
option.setAttribute('aria-checked', String(nextState));
} else {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
}
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (selectionMode === 'multiple') {
pulldownDemo.dataset.open = 'true';
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
} else {
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo'],
});
document.addEventListener('click', (event) => {
if (event.target.closest('.sg-pulldown-demo')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
</script>
</body>
</html>
@@ -0,0 +1,312 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Page Layout App</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout Page Layout App</h1>
<main aria-label="Page Layout App">
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="default" data-component-context="portal-header" data-pattern-part="portal-header-action">
<button class="sg-interaction-element sg-sandwich-button" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Admin</a>
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Logout</a>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Titel</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Gruppen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Listen</button>
</nav>
</div>
</header>
<div class="sg-options-row" aria-label="Optionszeile" data-pattern="options-row">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Sortierung" data-component-part="pulldown-trigger" data-label-base="Sortierung">
Sortierung
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Sortierung" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Sortierungsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Region" data-component-part="pulldown-trigger" data-label-base="Region">
Region
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Region" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Regionsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input
class="sg-interaction-element sg-input-single-line"
type="text"
placeholder="Suche"
aria-label="Suche"
>
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
<div class="sg-options-row__right" data-pattern-part="options-row-secondary-actions">
<button class="sg-mode-toggle" type="button" data-active="relative" aria-label="Modus Schieber global: relativ aktiv" data-component="mode-toggle" data-component-context="options-row">
<span class="sg-mode-toggle__label" data-component-part="toggle-label">absolut</span>
<span class="sg-mode-toggle__switch" aria-hidden="true" data-component-part="toggle-track">
<span class="sg-mode-toggle__handle" data-component-part="toggle-handle"></span>
</span>
<span class="sg-mode-toggle__label" data-component-part="toggle-label">relativ</span>
</button>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="options-row">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.
</span>
</span>
</div>
</div>
<div class="sg-transparent-card sg-page-layout-app__heading-block" aria-label="H1 Bereich" data-component="transparent-card">
<h1 class="sg-heading-h1 sg-text-on-dark">H1 Überschrift</h1>
<p class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--sixty-width">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer convallis purus sed urna ultricies, id aliquet justo malesuada. Morbi luctus, augue in cursus ultrices, justo lorem posuere mi, at suscipit est turpis vitae ipsum.
</p>
</div>
<div class="sg-group-card sg-group-card--margin-after-large" data-component="group-card">
<div class="sg-group-card__header-row">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading">Seiteninhalt</h2>
</div>
<article class="sg-card" data-component="card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Inhalt</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">
Zeile 01<br>
Zeile 02<br>
Zeile 03<br>
Zeile 04<br>
Zeile 05<br>
Zeile 06<br>
Zeile 07<br>
Zeile 08<br>
Zeile 09<br>
Zeile 10<br>
Zeile 11<br>
Zeile 12<br>
Zeile 13<br>
Zeile 14<br>
Zeile 15<br>
Zeile 16<br>
Zeile 17<br>
Zeile 18<br>
Zeile 19<br>
Zeile 20<br>
Zeile 21<br>
Zeile 22<br>
Zeile 23<br>
Zeile 24<br>
Zeile 25<br>
Zeile 26<br>
Zeile 27<br>
Zeile 28<br>
Zeile 29<br>
Zeile 30
</p>
</div>
</article>
</div>
</main>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs, .sg-portal-header__menu-wrap .sg-tab-button-group').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
updatePulldownSelectionState(demo);
});
demo.querySelectorAll('[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const nextChecked = option.getAttribute('aria-checked') !== 'true';
option.setAttribute('aria-checked', String(nextChecked));
updatePulldownSelectionState(demo);
});
});
updatePulldownSelectionState(demo);
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
</script>
</body>
</html>
@@ -0,0 +1,97 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Page Layout Public</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout Page Layout Public</h1>
<main aria-label="Page Layout Public">
<header class="sg-portal-header sg-portal-header--auth-segment" aria-label="Public Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__auth-row" data-pattern-part="portal-header-action">
<div class="sg-tab-button-group" role="tablist" aria-label="Anmeldung" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">
Login
</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">
Registrieren
</button>
</div>
</div>
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">Portalname</p>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Inhalt 1</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Inhalt 2</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Inhalt 3</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Inhalt 4</button>
</nav>
</div>
</header>
<div class="sg-page-layout-public__content-mirror">
<div class="sg-transparent-card sg-page-layout-public__heading-block" aria-label="H1 Bereich" data-component="transparent-card">
<h1 class="sg-heading-h1 sg-text-on-dark">H1 Überschrift</h1>
<p class="sg-body sg-text-layout-pattern__sample">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer convallis purus sed urna ultricies, id aliquet justo malesuada. Morbi luctus, augue in cursus ultrices, justo lorem posuere mi, at suscipit est turpis vitae ipsum.
</p>
</div>
<article class="sg-card sg-card--content-card sg-card--content-card-dark sg-page-layout-public__footer-card" data-component="content-card" aria-label="Fusszeile">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__two-column" data-pattern-part="text-block-two-column">
<p class="sg-body sg-text-layout-pattern__column">
Footer links: Platzhaltertext fuer allgemeine Hinweise, Navigation oder Kontaktinformationen im zweispaltigen Layout.
</p>
<p class="sg-body sg-text-layout-pattern__column">
Footer rechts: Platzhaltertext fuer ergaenzende Angaben, rechtliche Hinweise oder sekundäre Footer-Inhalte im gleichen Raster.
</p>
</div>
</div>
</article>
</div>
</main>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs, .sg-portal-header__auth-row .sg-tab-button-group, .sg-portal-header__menu-wrap .sg-tab-button-group').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
</script>
</body>
</html>
+483
View File
@@ -0,0 +1,483 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Portal Header</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Portal Header</h1>
<section id="pattern-portal-header">
<p class="sg-preview-label">Pattern: Portal Header</p>
<p class="sg-table-label sg-text-on-dark">
Desktop/Tablet: Header skaliert über die verfügbare Breite.
</p>
<p class="sg-table-label sg-text-on-dark">
Mobile: Sandwich-Menü und Portaltitel in der ersten Zeile; Navigationstasten in einer eigenen Zeile darunter.
</p>
<article class="sg-portal-header-pattern-variant" aria-label="Portal Header ohne Options Row">
<p class="sg-table-label sg-portal-header-pattern-variant__label sg-text-on-dark">
Variante: Portal Header ohne Options Row
</p>
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="default" data-component-context="portal-header" data-pattern-part="portal-header-action">
<button class="sg-interaction-element sg-sandwich-button" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Admin</a>
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Logout</a>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Titel</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Gruppen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Listen</button>
</nav>
</div>
</header>
<p class="sg-table-label sg-portal-header-pattern-variant__next-element sg-text-on-dark">
Nächstes Element (Abstand nach unten: spacing-large)
</p>
</article>
<section id="pattern-public-portal-header">
<h1 class="sg-main-heading">Pattern Public Portal Header</h1>
<p class="sg-preview-label">Pattern: Public Portal Header</p>
<p class="sg-table-label sg-text-on-dark">
Desktop/Tablet: Header skaliert über die verfügbare Breite.
</p>
<p class="sg-table-label sg-text-on-dark">
Mobile: Login/Registrieren und Portaltitel in der ersten Zeile; Navigationstasten in einer eigenen Zeile darunter.
</p>
<article class="sg-portal-header-pattern-variant" aria-label="Public Portal Header ohne Options Row">
<p class="sg-table-label sg-portal-header-pattern-variant__label sg-text-on-dark">
Variante: Public Portal Header ohne Options Row
</p>
<header class="sg-portal-header sg-portal-header--auth-segment" aria-label="Public Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__auth-row" data-pattern-part="portal-header-action">
<div class="sg-tab-button-group" role="tablist" aria-label="Anmeldung" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">
Login
</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">
Registrieren
</button>
</div>
</div>
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">Portalname</p>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Inhalt 1</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Inhalt 2</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Inhalt 3</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Inhalt 4</button>
</nav>
</div>
</header>
<p class="sg-table-label sg-portal-header-pattern-variant__next-element sg-text-on-dark">
Nächstes Element (Abstand nach unten: spacing-large)
</p>
</article>
</section>
<article class="sg-portal-header-pattern-variant" aria-label="Portal Header mit Options Row">
<p class="sg-table-label sg-portal-header-pattern-variant__label sg-text-on-dark">
Variante: Portal Header mit Options Row
</p>
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="default" data-component-context="portal-header" data-pattern-part="portal-header-action">
<button class="sg-interaction-element sg-sandwich-button" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Admin</a>
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Logout</a>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Titel</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Gruppen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Listen</button>
</nav>
</div>
</header>
<div class="sg-options-row" aria-label="Optionszeile" data-pattern="options-row">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Sortierung" data-component-part="pulldown-trigger" data-label-base="Sortierung">
Sortierung
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Sortierung" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Sortierungsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Region" data-component-part="pulldown-trigger" data-label-base="Region">
Region
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Region" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Regionsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input
class="sg-interaction-element sg-input-single-line"
type="text"
placeholder="Suche"
aria-label="Suche"
>
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
<div class="sg-options-row__right" data-pattern-part="options-row-secondary-actions">
<button class="sg-mode-toggle" type="button" data-active="relative" aria-label="Modus Schieber global: relativ aktiv" data-component="mode-toggle" data-component-context="options-row">
<span class="sg-mode-toggle__label" data-component-part="toggle-label">absolut</span>
<span class="sg-mode-toggle__switch" aria-hidden="true" data-component-part="toggle-track">
<span class="sg-mode-toggle__handle" data-component-part="toggle-handle"></span>
</span>
<span class="sg-mode-toggle__label" data-component-part="toggle-label">relativ</span>
</button>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="options-row">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.
</span>
</span>
</div>
</div>
<p class="sg-table-label sg-portal-header-pattern-variant__next-element sg-text-on-dark">
Nächstes Element (Abstand nach unten: spacing-large)
</p>
</article>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs, .sg-portal-header__auth-row .sg-tab-button-group, .sg-portal-header__menu-wrap .sg-tab-button-group').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-checkbox-field').forEach((checkbox) => {
checkbox.addEventListener('click', (event) => {
event.stopPropagation();
if (checkbox.disabled) {
return;
}
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
const pulldownDemo = checkbox.closest('.sg-pulldown-demo');
if (pulldownDemo) {
updatePulldownSelectionState(pulldownDemo);
pulldownDemo.dataset.open = 'true';
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
const selectionMode = pulldownDemo.dataset.selectionMode || 'single';
if (selectionMode === 'multiple') {
const nextState = option.getAttribute('aria-checked') !== 'true';
option.setAttribute('aria-checked', String(nextState));
} else {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
}
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (selectionMode === 'multiple') {
pulldownDemo.dataset.open = 'true';
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
} else {
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
if (!input || !clearButton) {
return;
}
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
if (event.target.closest('.sg-pulldown-demo')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,64 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern Text Layouts</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern Text Layouts</h1>
<section id="pattern-text-layouts">
<p class="sg-preview-label">Pattern: Text Layout ganze Breite</p>
<div class="sg-text-layout-pattern" data-pattern="text-layout-ganze-breite" aria-label="Text Layout ganze Breite">
<div class="sg-text-layout-pattern__preview-surface">
<p class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--full-width" data-pattern-part="text-block-full-width">
Text 100% Breite: Dieser Textblock nimmt die komplette verfügbare Breite des Containers ein und eignet sich für lineare Erklärungen, einleitende Kontexte und Fliesstext ohne Nebenspalte.
</p>
</div>
</div>
<p class="sg-preview-label">Pattern: Text 60% Breite</p>
<div class="sg-text-layout-pattern" data-pattern="text-layout-sechzig-prozent" aria-label="Text Layout 60 Prozent Breite">
<div class="sg-text-layout-pattern__preview-surface">
<p class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--sixty-width" data-pattern-part="text-block-sixty-width">
Text 60% Breite: Dieser Textblock nutzt sechzig Prozent der Containerbreite und schafft dadurch bewusst mehr Luft für begleitende Inhalte wie Metriken, Visuals oder Kontextinformationen.
</p>
</div>
</div>
<p class="sg-preview-label">Pattern: Text zweispaltig (mobil zweispaltig)</p>
<div class="sg-text-layout-pattern" data-pattern="text-layout-zweispaltig" aria-label="Text Layout zweispaltig mobil zweispaltig">
<div class="sg-text-layout-pattern__preview-surface">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__two-column" data-pattern-part="text-block-two-column">
<p class="sg-body sg-text-layout-pattern__column">
Text zweispaltig links: Dieses Layout teilt den Text in zwei gleich breite Spalten mit jeweils fünfzig Prozent Breite auf. Es eignet sich besonders für längere inhaltliche Abschnitte, in denen eine kompaktere Zeilenlänge die Lesbarkeit verbessert.
</p>
<p class="sg-body sg-text-layout-pattern__column">
Text zweispaltig rechts: Gleichzeitig bleibt die Informationsdichte hoch, ohne dass der visuelle Rhythmus in langen einspaltigen Textflächen verloren geht. Durch die symmetrische Aufteilung entstehen klare Leseachsen für Analyse- und Detailseiten.
</p>
</div>
</div>
</div>
<p class="sg-preview-label">Pattern: Dreispaltig verteilt (mobil dreispaltig)</p>
<div class="sg-text-layout-pattern" data-pattern="text-layout-dreispaltig-verteilt" aria-label="Text Layout dreispaltig verteilt mobil dreispaltig">
<div class="sg-text-layout-pattern__preview-surface">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__three-column-distributed" data-pattern-part="text-block-three-column-distributed">
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-left">
Linke Spalte linksbündig: Diese Spalte führt den Inhalt mit einer klaren Startkante und eignet sich für einleitende Aussagen oder Kontext, die direkt in den Lesefluss einsteigen sollen.
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-center">
Mittlere Spalte mittelachsig: Diese Spalte zentriert den Text auf der Mittelachse und eignet sich für vergleichende Kernaussagen, die visuell als Schwerpunkt zwischen den Randspalten stehen sollen.
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-right">
Rechte Spalte rechtsbündig: Diese Spalte schließt den Abschnitt mit einer klaren Endkante ab und eignet sich für ergänzende Hinweise oder abschließende Perspektiven im gleichen Inhaltsblock.
</p>
</div>
</div>
</div>
</section>
</body>
</html>
@@ -0,0 +1,469 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF Card Listen Seite Fundamentalanalyse Drawer</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF Fundamentalanalyse Drawer</h1>
<section class="sg-card-list-page" aria-label="VSF Fundamentalanalyse Drawer Seite">
<div class="sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigation Zurück zur Liste oben">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="./vsf-card-listen-seite.html" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
<article class="sg-card sg-vsf-drawer-card" data-component="card" data-pattern="card" aria-label="Card Western Digital Corporation">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-inline-header-row" data-pattern-part="card-header">
<h2 class="sg-heading-h2 sg-vsf-drawer-object-card__heading">Western Digital Corporation</h2>
<div class="sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Objekt-Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Objekt-Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-vsf-drawer-card__content" data-pattern-part="card-body">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__two-column" data-pattern-part="text-block-two-column">
<p class="sg-body sg-text-layout-pattern__column">
Ticker: WDC<br>
Region: Amerika<br>
Sub-Region: Nordamerika<br>
Land: Vereinigte Staaten von Amerika
</p>
<p class="sg-body sg-text-layout-pattern__column">
ISIN: US9581021055<br>
Industrie: Computer Hardware
</p>
</div>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-vsf-drawer-card__actions-segment" data-pattern-part="card-actions">
<p id="vsf-business-model-text" class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--full-width" data-pattern-part="text-block-full-width" hidden>
Produkte und Lösungen<br>
Western Digital Corporation produziert Festplatten (Hard Disk Drives, HDD) in verschiedenen Formfaktoren und Kapazitäten. Zentrale Produktkategorien umfassen Marken wie WD Blue für allgemeine Computing-Anwendungen, WD Purple und WD Purple Pro für Überwachungssysteme, WD Red, WD Red Plus und WD Red Pro für Netzwerkspeicher (NAS) und kleine Büroumgebungen, WD Black für Gaming und Hochleistung, WD Gold für Unternehmensanwendungen sowie Ultrastar für Rechenzentren. Ergänzt werden diese durch externe Gehäuse wie My Passport und My Book sowie Netzwerkspeichersysteme der My Cloud-Serie. Seit der Abspaltung des Flash-Geschäfts 2025 konzentriert sich das Unternehmen ausschliesslich auf HDD-Technologien.<br><br>
Zielmärkte und Kunden<br>
Die wichtigsten Kundensegmente umfassen Endverbraucher für Desktop-Computer und Gaming, kleine Büros und Home-Office-Nutzer für NAS-Systeme, Überwachungsanbieter für Sicherheitslösungen, Unternehmen für Server- und Enterprise-Speicher sowie Rechenzentren für Hochkapazitäts-Datenspeicherung. Geografisch bedient das Unternehmen globale Märkte mit Schwerpunkt in Nordamerika, Europa, Asien-Pazifik und aufstrebenden Regionen wie Südostasien. Die Produktion erfolgt primär in Thailand und früher in Malaysia.<br><br>
Geschäftsmodell<br>
Western Digital agiert als vertikal integrierter Hersteller von Datenspeichergeräten mit Fokus auf Festplatten-Produktion. Die Wertschöpfungskette umfasst Forschung und Entwicklung zu magnetischen Aufzeichnungstechnologien, Fertigung von Platten, Köpfen und Controllern sowie Montage in San-Jose-basierten Einrichtungen und asiatischen Fabriken. Durch Akquisitionen wie HGST (2012) und frühere Read-Rite hat es Assets in Medien und Komponenten aufgebaut, was eine hohe Eigenfertigung ermöglicht. Das Modell basiert auf skalierbarer Massenproduktion für standardisierte Speicherplattformen.<br><br>
Erlösstruktur<br>
Die Umsatzquellen gliedern sich in Segmente für Client-Computing (WD Blue, WD Black), Überwachung (WD Purple), NAS und SMB (WD Red-Serie), Enterprise (WD Gold) und Rechenzentren (Ultrastar). Der Verkauf erfolgt mechanisch über Direktverkäufe an Original Equipment Manufacturer (OEM), Distributoren und Einzelhändler sowie über eigene Kanäle für externe Produkte. Die Konzentration liegt bei Enterprise- und Datacenter-Segmenten mit höheren Kapazitäten, während Client-Segmente Volumen generieren; keine Abonnements oder transaktionsbasierten Modelle.<br><br>
Marktposition und Wettbewerbsvorteile<br>
Western Digital hält eine führende Marktstellung als grösster traditioneller HDD-Hersteller weltweit nach der HGST-Integration. Strukturelle Vorteile umfassen vertikale Integration in Plattenmedien, Schreib-/Leseköpfe und Controller-Technologie sowie heliumgefüllte Hochkapazitätsmodelle bis 14 TB. Patente in Riesen-Magnetowiderstands-Köpfen (GMR) und Produktionskapazitäten in effizienten Fabriken sichern Skaleneffekte gegenüber Wettbewerbern.
</p>
<button id="vsf-business-model-toggle" class="sg-interaction-element sg-button" type="button" aria-controls="vsf-business-model-text" aria-expanded="false">Geschäftsmodell anzeigen</button>
</footer>
</article>
<div class="sg-transparent-card sg-analysis-intro-block" data-component="transparent-card">
<h2 class="sg-heading-h2 sg-text-on-dark sg-card-list-page__title-row">Fundamentalanalyse vom 12.5.2026 <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></h2>
<p class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--full-width" data-pattern-part="text-block-full-width">
Nächste Analyse: nicht bekannt
</p>
</div>
<div class="sg-content-block-card-group" data-pattern="card-gruppe-mit-tastennavigation">
<div class="sg-tab-button-group sg-content-block-card-group__tabs" role="tablist" aria-label="Fundamentalanalyse Navigation" data-component="tab-navigation" data-component-size="large">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="true" aria-controls="vsf-fundamental-tab-panel-1" id="vsf-fundamental-tab-1" data-component-part="tab-button">Gesamtbewertung</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-2" id="vsf-fundamental-tab-2" data-component-part="tab-button">Marktbewertung</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-3" id="vsf-fundamental-tab-3" data-component-part="tab-button">Wachstum</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-4" id="vsf-fundamental-tab-4" data-component-part="tab-button">Profitabilität</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-5" id="vsf-fundamental-tab-5" data-component-part="tab-button">Stabilität</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-6" id="vsf-fundamental-tab-6" data-component-part="tab-button">TAM</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-7" id="vsf-fundamental-tab-7" data-component-part="tab-button">Zyklus</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-8" id="vsf-fundamental-tab-8" data-component-part="tab-button">Moat</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-fundamental-tab-panel-9" id="vsf-fundamental-tab-9" data-component-part="tab-button">Technologie</button>
</div>
<div class="sg-content-block-card-group__panels">
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-1" role="tabpanel" aria-labelledby="vsf-fundamental-tab-1">
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Card Gruppe Gesamtbewertung">
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Gesamtbewertung">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Gesamtbewertung <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<div class="sg-score-bar-list sg-score-bar-list--single-score" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state"><strong class="sg-score-bar-label">(9.9)</strong> attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--white" data-component-part="card-body">
<p class="sg-body">
<strong>Attraktiv. Starke Profitabilitäts- und Gewinnwachstumsdynamik durch KI-Nachfrage, solide Bilanz. Ambitionierte Bewertung setzt perfekte Ausführung voraus.</strong>
</p>
<p class="sg-body">
Western Digital repräsentiert eine hochgradig zyklische, aber aktuell von einem strukturellen KI-Trend getragene Wachstumsgeschichte. Die zentrale Investment-These lautet: Das Unternehmen profitiert kurzfristig von einer außergewöhnlichen Nachfrage nach Hochkapazitäts-Festplatten für Rechenzentren, was zu einer explosiven Ergebnisdynamik führt, jedoch zu einer ambitionierten Bewertung, die eine perfekte Ausführung voraussetzt.
</p>
<p class="sg-body">
Die entscheidenden Stärken sind die starke Profitabilitätsverbesserung mit hohen Kapitalrenditen, die robuste finanzielle Stabilität mit niedriger Verschuldung sowie die Skalenvorteile aus der vertikalen Integration. Dem stehen klare Risiken gegenüber: ein extrem hohes zyklisches Risiko mit Nachfrageschwankungen bis zu 40 Prozent, eine nur mäßig verteidigungsfähige Wettbewerbsposition unter Druck durch chinesische Konkurrenten und disruptive Technologien sowie eine Bewertung, die einen erheblichen Aufschlag auf das historische Niveau darstellt. Der zentrale Zielkonflikt liegt zwischen dem außergewöhnlichen kurzfristigen Wachstum und der langfristigen Nachhaltigkeit des Geschäftsmodells in einer hart umkämpften, kapitalintensiven Branche.
</p>
<p class="sg-body">
Die Qualität des Unternehmens ist aufgrund seiner zyklischen Natur und der angreifbaren Wettbewerbsvorteile als mittel einzustufen. Für einen GARP-Investor ist das Investment aktuell neutral bis nicht attraktiv. Zwar sind Timing-Faktoren durch die zyklische Erholung und den KI-Rückenwind günstig, die extreme Bewertung (KGV nahe 44-47) spiegelt dieses Momentum jedoch bereits vollständig wider und bietet kein Margin of Safety. Das Investment eignet sich nur für risikobewusste Anleger, die auf eine Fortsetzung des KI-Superzyklus spekulieren.
</p>
</div>
</article>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Unterschied zur letzten Analyse vom 1.5.2026">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Unterschied zur letzten Analyse vom 1.5.2026 <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Die Investment-Story hat sich insgesamt verbessert, da sich die strukturellen Wachstumstreiber konkretisiert und die Profitabilität deutlich beschleunigt hat.</p>
<p class="sg-body">Die entscheidenden Veränderungen betreffen das Wachstumstempo und die Margenentwicklung. Das Umsatzwachstum beschleunigte sich auf 25 Prozent im Jahresvergleich, angetrieben von einer 28-prozentigen Zunahme im Cloud-Segment. Die bereinigte Bruttomarge stieg um 770 Basispunkte auf 46,1 Prozent, unterstützt durch eine günstigere Produktmischung und Skaleneffekte. Zudem wurde die Prognose für das kommende Quartal auf ein Wachstum von 40 Prozent angehoben, was auf eine anhaltend starke Nachfrage hindeutet.</p>
<p class="sg-body">Diese Entwicklungen bestätigen die ursprüngliche These eines durch künstliche Intelligenz getriebenen Speicher-Superzyklus und stärken sie sogar. Das verbesserte Profitabilitätsniveau und die höheren Wachstumsprognosen senken das Risiko einer kurzfristigen zyklischen Abschwächung, während die Bewertung weiterhin unter dem historischen Durchschnitt bleibt. Das Chance-Risiko-Profil hat sich somit zugunsten der Chancen verschoben.</p>
</div>
</article>
</div>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-2" role="tabpanel" aria-labelledby="vsf-fundamental-tab-2" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Marktbewertung">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Marktbewertung <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<div class="sg-score-bar-list sg-score-bar-list--single-score" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state"><strong class="sg-score-bar-label">(9.9)</strong></p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<table class="sg-data-table" aria-label="Marktbewertung Kennzahlen" data-component="data-table">
<thead>
<tr>
<th data-component-part="data-table-header-cell">Kennzahl</th>
<th data-component-part="data-table-header-cell">Wert</th>
</tr>
</thead>
<tbody>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">27.5</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">26.4</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PEG Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value sg-score-state--positive" data-component-part="data-table-value-cell">0.35</td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body">
<p class="sg-body">Graphik</p>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Die Aktie von Western Digital wird mit einem Kurs-Gewinn-Verhältnis für das nächste Jahr von knapp 44 bis 47 bewertet. Dies stellt einen erheblichen Aufschlag gegenüber dem historischen Fünfjahresdurchschnitt von etwa 19,5 dar. Diese Bewertungsprämie ist durch ein außergewöhnliches Ergebniswachstum begründet. Für das Geschäftsjahr 2026 erwarten Analysten ein bereinigtes Ergebnis je Aktie, das sich um etwa 81 bis 100 Prozent erhöht. Für das folgende Jahr wird ein weiterer Anstieg auf rund 13,82 Dollar prognostiziert.</p>
<p class="sg-body">Diese Dynamik speist sich aus der künstlichen Intelligenz getriebenen Nachfrage nach hochkapazitiven Festplatten in Rechenzentren. Das Cloud-Segment des Unternehmens wächst mit 28 bis 40 Prozent im Jahresvergleich und generiert mittlerweile 89 Prozent der Umsätze. Zugleich expandiert die Bruttomarge strukturell, was auf Preissetzungsmacht und positive Produktmix-Effekte hindeutet. Das gegenwärtige Bewertungsniveau setzt allerdings eine perfekte Ausführung voraus. Sollte der Superzyklus der künstlichen Intelligenz schwächer ausfallen oder es zu Überkapazitäten bei Halbleitern kommen, droht eine Bewertungskorrektur. Zum jetzigen Zeitpunkt erscheint die Bewertung ambitioniert, aber im Kontext des beschleunigten Ergebniswachstums nicht irrational. Anleger zahlen für das Umsatzmomentum und die strukturelle Margenverbesserung, nicht jedoch für überdurchschnittliche Langzeitgewinne.</p>
</div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-3" role="tabpanel" aria-labelledby="vsf-fundamental-tab-3" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Wachstum">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Wachstum <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<div class="sg-score-bar-list sg-score-bar-list--single-score" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state"><strong class="sg-score-bar-label">(9.9)</strong></p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<table class="sg-data-table" aria-label="Wachstum Kennzahlen" data-component="data-table">
<thead>
<tr>
<th data-component-part="data-table-header-cell">Kennzahl</th>
<th data-component-part="data-table-header-cell">Wert</th>
</tr>
</thead>
<tbody>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">27.5</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">26.4</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PEG Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value sg-score-state--positive" data-component-part="data-table-value-cell">0.35</td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body">
<p class="sg-body">Graphik</p>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body">Inhalt Wachstum.</p></div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-4" role="tabpanel" aria-labelledby="vsf-fundamental-tab-4" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Profitabilität">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Profitabilität <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<div class="sg-score-bar-list sg-score-bar-list--single-score" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state"><strong class="sg-score-bar-label">(9.9)</strong></p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<table class="sg-data-table" aria-label="Profitabilität Kennzahlen" data-component="data-table">
<thead>
<tr>
<th data-component-part="data-table-header-cell">Kennzahl</th>
<th data-component-part="data-table-header-cell">Wert</th>
</tr>
</thead>
<tbody>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">27.5</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">26.4</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PEG Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value sg-score-state--positive" data-component-part="data-table-value-cell">0.35</td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body">
<p class="sg-body">Graphik</p>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body">Inhalt Profitabilität.</p></div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-5" role="tabpanel" aria-labelledby="vsf-fundamental-tab-5" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Stabilität">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Stabilität <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<div class="sg-score-bar-list sg-score-bar-list--single-score" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state"><strong class="sg-score-bar-label">(9.9)</strong></p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--gray" data-component-part="card-body">
<table class="sg-data-table" aria-label="Stabilität Kennzahlen" data-component="data-table">
<thead>
<tr>
<th data-component-part="data-table-header-cell">Kennzahl</th>
<th data-component-part="data-table-header-cell">Wert</th>
</tr>
</thead>
<tbody>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">27.5</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PE Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value" data-component-part="data-table-value-cell">26.4</td>
</tr>
<tr>
<td class="sg-data-table__label" data-component-part="data-table-label-cell">PEG Forward <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="data-table"><button class="sg-help-icon sg-data-table__help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></td>
<td class="sg-data-table__value sg-score-state--positive" data-component-part="data-table-value-cell">0.35</td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body">
<p class="sg-body">Graphik</p>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body">Inhalt Stabilität.</p></div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-6" role="tabpanel" aria-labelledby="vsf-fundamental-tab-6" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="TAM">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">TAM <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Der Gesamtmarkt für Datenspeicher wird bis zum Jahr 2030 auf rund 200 Milliarden Dollar geschätzt. Dieses Wachstum wird durch exponentiell steigende Datenmengen angetrieben, wobei Technologien wie Cloud-Computing und das Training künstlicher Intelligenz zentrale Treiber mit hohen jährlichen Zuwachsraten sind. Western Digital adressiert primär den NAND-Flash-Markt, in dem es mit einem Marktanteil von 15 bis 18 Prozent hinter dem Marktführer liegt.</p>
<p class="sg-body">Der Wettbewerb ist in einem Dreier-Oligopol intensiv und verschärft sich durch den Kapazitätsausbau eines chinesischen Konkurrenten. Realistischer Expansionsspielraum für Western Digital wird auf einen Marktanteilsgewinn von 5 bis 10 Prozent geschätzt, angetrieben durch überdurchschnittliches Bit-Wachstum und die positive Nachfrage im KI-Bereich. Der direkt adressierbare Markt konzentriert sich auf Enterprise-SSDs und Cloud-Speicher, da der Markt für Consumer-Festplatten rückläufig ist. Überproportionales Wachstum wäre möglich, wenn das Unternehmen KI-spezifische Speicherlösungen priorisiert, allerdings limitiert Preisdruck die Marge.</p>
</div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-7" role="tabpanel" aria-labelledby="vsf-fundamental-tab-7" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Zyklus">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Zyklus <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body">Inhalt Zyklus.</p></div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-8" role="tabpanel" aria-labelledby="vsf-fundamental-tab-8" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Moat">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Moat <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body">Inhalt Moat.</p></div>
</article>
</div>
<div class="sg-content-block-card-group__panel" id="vsf-fundamental-tab-panel-9" role="tabpanel" aria-labelledby="vsf-fundamental-tab-9" hidden>
<article class="sg-card sg-card--content-card sg-card--overlay-host" data-component="content-card" aria-label="Technologie">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header"><div class="sg-strong">Technologie <span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon"><button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button><span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.</span></span></div></div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body"><p class="sg-body">Inhalt Technologie.</p></div>
</article>
</div>
</div>
</div>
<div class="sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigation Zurück zur Liste unten">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="./vsf-card-listen-seite.html" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
const businessModelToggleButton = document.getElementById('vsf-business-model-toggle');
const businessModelText = document.getElementById('vsf-business-model-text');
if (businessModelToggleButton && businessModelText) {
businessModelToggleButton.addEventListener('click', () => {
const isExpanded = businessModelToggleButton.getAttribute('aria-expanded') === 'true';
const nextExpanded = !isExpanded;
businessModelText.hidden = !nextExpanded;
businessModelToggleButton.setAttribute('aria-expanded', String(nextExpanded));
businessModelToggleButton.textContent = nextExpanded
? 'Geschäftsmodell ausblenden'
: 'Geschäftsmodell anzeigen';
});
}
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-sandwich-menu-wrap'],
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
});
const tabGroup = document.querySelector('[data-pattern="card-gruppe-mit-tastennavigation"]');
if (tabGroup) {
const tabs = Array.from(tabGroup.querySelectorAll('[role="tab"]'));
const panels = Array.from(tabGroup.querySelectorAll('[role="tabpanel"]'));
const activateTab = (targetTab) => {
tabs.forEach((tab) => {
tab.setAttribute('aria-selected', String(tab === targetTab));
});
panels.forEach((panel) => {
panel.hidden = panel.id !== targetTab.getAttribute('aria-controls');
});
};
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
activateTab(tab);
});
tab.addEventListener('keydown', (event) => {
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') {
return;
}
event.preventDefault();
const direction = event.key === 'ArrowRight' ? 1 : -1;
const nextIndex = (index + direction + tabs.length) % tabs.length;
const nextTab = tabs[nextIndex];
nextTab.focus();
activateTab(nextTab);
});
});
}
</script>
</body>
</html>
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF Card Listen Seite Fundamentalanalyse Mobile</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF Fundamentalanalyse Mobile</h1>
<section class="sg-card-list-page" aria-label="VSF Fundamentalanalyse Seite mobil">
<div class="sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigation Zurück zur Liste oben">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="./vsf-card-listen-seite.html" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
<div class="sg-content-cards-group" data-pattern="content-cards-group" aria-label="Fundamentalanalyse Karten Gruppe mobil">
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Fundamentalanalyse Box 1 mobil">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 1</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum. Integer feugiat, sem a iaculis lacinia, augue libero pretium orci, in dictum eros nibh et risus. Fusce sagittis, dolor ut facilisis tincidunt, lorem nisi sodales sem.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Fundamentalanalyse Box 2 mobil">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 2</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum. Integer feugiat, sem a iaculis lacinia, augue libero pretium orci, in dictum eros nibh et risus. Fusce sagittis, dolor ut facilisis tincidunt, lorem nisi sodales sem.</p>
</div>
</article>
<article class="sg-card sg-card--content-card" data-component="content-card" aria-label="Fundamentalanalyse Box 3 mobil">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue" data-component-part="card-header">
<div class="sg-strong">Box 3</div>
</div>
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec fermentum sapien ut nibh egestas, sed ultrices quam vestibulum. Integer feugiat, sem a iaculis lacinia, augue libero pretium orci, in dictum eros nibh et risus. Fusce sagittis, dolor ut facilisis tincidunt, lorem nisi sodales sem.</p>
</div>
</article>
</div>
<div class="sg-navigation-card-layout">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigation Zurück zur Liste unten">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="./vsf-card-listen-seite.html" data-component="hyperlink">zurück zur Liste</a>
</div>
</div>
</article>
</div>
</div>
</section>
</body>
</html>
@@ -0,0 +1,926 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF Card Listen Seite</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF Card Listen Seite</h1>
<section class="sg-card-list-page" aria-label="VSF Card Listen Seite">
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="default" data-component-context="portal-header" data-pattern-part="portal-header-action">
<button class="sg-interaction-element sg-sandwich-button" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Admin</a>
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Logout</a>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Titel</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Gruppen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Listen</button>
</nav>
</div>
</header>
<div class="sg-options-row" aria-label="Optionszeile" data-pattern="options-row">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Sortierung" data-component-part="pulldown-trigger" data-label-base="Sortierung">
Sortierung
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Sortierung" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Sortierungsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 1</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 2</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 3</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 4</span></li>
<li class="sg-pulldown-option sg-pulldown-option--disabled"><span>Menüpunkt 5</span></li>
</ul>
</div>
</div>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Region" data-component-part="pulldown-trigger" data-label-base="Region">
Region
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Region" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Regionsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 1</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 2</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 3</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 4</span></li>
<li class="sg-pulldown-option sg-pulldown-option--disabled"><span>Menüpunkt 5</span></li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input class="sg-interaction-element sg-input-single-line" type="text" placeholder="Suche" aria-label="Suche">
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
<div class="sg-options-row__right" data-pattern-part="options-row-secondary-actions">
<button class="sg-mode-toggle" type="button" data-active="relative" aria-label="Modus Schieber global: relativ aktiv" data-component="mode-toggle" data-component-context="options-row">
<span class="sg-mode-toggle__label" data-component-part="toggle-label">absolut</span>
<span class="sg-mode-toggle__switch" aria-hidden="true" data-component-part="toggle-track">
<span class="sg-mode-toggle__handle" data-component-part="toggle-handle"></span>
</span>
<span class="sg-mode-toggle__label" data-component-part="toggle-label">relativ</span>
</button>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="options-row">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.
</span>
</span>
</div>
</div>
<div class="sg-transparent-card sg-card-list-page__intro-block" aria-label="Einleitung zur VSF Listenübersicht" data-component="transparent-card">
<div class="sg-card-list-page__title-row">
<h1 class="sg-heading-h1 sg-text-on-dark sg-card-list-page__title">VSF Listenübersicht</h1>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="layout-card-list-page">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext zur VSF Listenübersicht anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Hilfe zur Listenansicht und zu den Filteroptionen.
</span>
</span>
</div>
<p class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--sixty-width">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer convallis purus sed urna ultricies, id aliquet justo malesuada. Morbi luctus, augue in cursus ultrices, justo lorem posuere mi, at suscipit est turpis vitae ipsum. Praesent posuere nisl a nisl fermentum, nec feugiat odio volutpat. Nam id dictum justo, eget dapibus arcu. Fusce varius justo nec nibh gravida, sed dignissim est tempor.</p>
</div>
<div class="sg-object-card-grid sg-card-list-page__object-grid" aria-label="Company Liste">
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-strong">Netflix, Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<table class="sg-data-table" aria-label="Company Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td data-component-part="data-columns-value-cell">PE: <span class="sg-data-table__value">28.8</span></td>
<td data-component-part="data-columns-reference-cell">PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span></td>
</tr>
<tr>
<td data-component-part="data-columns-value-cell">PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span></td>
<td data-component-part="data-columns-reference-cell"></td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-strong">Netflix, Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<table class="sg-data-table" aria-label="Company Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td data-component-part="data-columns-value-cell">PE: <span class="sg-data-table__value">28.8</span></td>
<td data-component-part="data-columns-reference-cell">PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span></td>
</tr>
<tr>
<td data-component-part="data-columns-value-cell">PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span></td>
<td data-component-part="data-columns-reference-cell"></td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-strong">Netflix, Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<table class="sg-data-table" aria-label="Company Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td data-component-part="data-columns-value-cell">PE: <span class="sg-data-table__value">28.8</span></td>
<td data-component-part="data-columns-reference-cell">PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span></td>
</tr>
<tr>
<td data-component-part="data-columns-value-cell">PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span></td>
<td data-component-part="data-columns-reference-cell"></td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-strong">Netflix, Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<table class="sg-data-table" aria-label="Company Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td data-component-part="data-columns-value-cell">PE: <span class="sg-data-table__value">28.8</span></td>
<td data-component-part="data-columns-reference-cell">PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span></td>
</tr>
<tr>
<td data-component-part="data-columns-value-cell">PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span></td>
<td data-component-part="data-columns-reference-cell"></td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-strong">Netflix, Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<table class="sg-data-table" aria-label="Company Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td data-component-part="data-columns-value-cell">PE: <span class="sg-data-table__value">28.8</span></td>
<td data-component-part="data-columns-reference-cell">PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span></td>
</tr>
<tr>
<td data-component-part="data-columns-value-cell">PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span></td>
<td data-component-part="data-columns-reference-cell"></td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="company-card" aria-label="Company Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="company-card-header">
<div class="sg-strong">Netflix, Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-score">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Score:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--lightgrey" data-pattern-part="company-card-data-columns">
<table class="sg-data-table" aria-label="Company Kennzahlen-Spalten" data-component="data-columns">
<tbody>
<tr>
<td data-component-part="data-columns-value-cell">PE: <span class="sg-data-table__value">28.8</span></td>
<td data-component-part="data-columns-reference-cell">PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span></td>
</tr>
<tr>
<td data-component-part="data-columns-value-cell">PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span></td>
<td data-component-part="data-columns-reference-cell"></td>
</tr>
</tbody>
</table>
</div>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Fundamentalanalyse vom 8.5.2026:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
<div class="sg-score-bar__median-marker" data-component-part="score-median-marker"></div>
</div>
</div>
<div class="sg-company-card__moat-row">
<span class="sg-score-bar-label sg-bar-label sg-company-card__moat-label">Moat:</span>
<span class="sg-bar-label sg-company-card__moat-neutral sg-company-card__moat-value">Maessig</span>
</div>
</div>
<p class="sg-body sg-company-card__summary">
Attraktiv: Exzellente Profitabilität und starkes Wachstum jenseits des Flaggschiffs. Wichtigstes Risiko ist die hohe Abhängigkeit von einem Produkt mit nahendem Patentablauf.
</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="company-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<div class="sg-navigation-card-layout sg-card-list-page__navigation">
<div class="sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">mehr laden</a>
</div>
</div>
</article>
</div>
</div>
</div>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
const updateObjectCardGridRowState = () => {
document.querySelectorAll('.sg-object-card-grid').forEach((grid) => {
const cards = Array.from(grid.querySelectorAll('.sg-object-card'));
grid.classList.remove('sg-object-card-grid--multi-row');
grid.style.removeProperty('--layout-object-card-shared-width');
if (cards.length <= 1) {
return;
}
const firstTop = cards[0].offsetTop;
const hasMultipleRows = cards.some((card) => card.offsetTop !== firstTop);
if (!hasMultipleRows) {
return;
}
const firstRowCards = cards.filter((card) => card.offsetTop === firstTop);
const referenceCard = firstRowCards[0];
if (!referenceCard) {
return;
}
const referenceWidth = referenceCard.getBoundingClientRect().width;
grid.style.setProperty('--layout-object-card-shared-width', `${referenceWidth}px`);
grid.classList.add('sg-object-card-grid--multi-row');
});
};
document.querySelectorAll('.sg-portal-header__tabs').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
if (!input || !clearButton) {
return;
}
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
const mobileBreakpoint = window.matchMedia('(max-width: 767px)');
document.querySelectorAll('.sg-object-card__action').forEach((button) => {
if (button.textContent?.trim() !== 'Fundamentalanalyse') {
return;
}
button.addEventListener('click', () => {
if (mobileBreakpoint.matches) {
window.location.href = './vsf-card-listen-fundamentalanalyse-mobile.html';
return;
}
window.location.href = './vsf-card-listen-fundamentalanalyse-drawer.html';
});
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
if (!event.target.closest('.sg-pulldown-demo')) {
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
}
});
window.addEventListener('load', updateObjectCardGridRowState);
window.addEventListener('resize', updateObjectCardGridRowState);
</script>
</body>
</html>
+507
View File
@@ -0,0 +1,507 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Layout VSF List Card</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF List Card</h1>
<section id="layout-vsf-list-card">
<p class="sg-preview-label">Layout: VSF List Card</p>
<p class="sg-body">Diese Layout-Seite zeigt VSF-spezifische List-Card-Layouts als Grundlage fuer weitere Ausdifferenzierungen.</p>
<h2 class="sg-sub-heading sg-section-h2">Layout 1: Neue Liste anlegen Karte</h2>
<div class="sg-object-card-grid" aria-label="VSF List Card Layout Neue Liste anlegen">
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Neue Liste anlegen Karte">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header">
<div class="sg-strong">Neue Liste anlegen</div>
</header>
<div class="sg-card-segment sg-card-segment--gray sg-object-card__content" data-pattern-part="object-group-card-content">
<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Formular mit Abschnitten">
<form class="sg-form-sections-card" action="#" method="post" aria-label="Neue Liste anlegen Formular">
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<section class="sg-form-sections-card__chapter" aria-label="Neue Liste">
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Listentyp</span>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="form" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown--inactive-selectable sg-form-inactive-selectable sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown ohne aktive Auswahl" data-component-part="pulldown-trigger" data-label-base="Listentyp">
Listentyp
</button>
<div class="sg-pulldown-panel" aria-label="Geoeffnetes Pulldown Listentyp" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Listentyp Optionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Watchlist</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Portfolio</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Screening</span></li>
</ul>
</div>
</div>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Name</span>
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
placeholder="Name eingeben"
aria-label="Name"
maxlength="80"
>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Beschreibung</span>
<textarea
class="sg-input-multi-line sg-form-inactive-selectable"
rows="4"
placeholder="Beschreibung eingeben"
aria-label="Beschreibung"
maxlength="350"
></textarea>
</label>
</div>
</section>
</div>
<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">
<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">
<button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button">Zuruecksetzen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-form-sections-card__action" type="submit" disabled aria-disabled="true">Liste anlegen</button>
</div>
</footer>
</form>
</div>
</div>
</article>
</div>
<h2 class="sg-sub-heading sg-section-h2">Layout 2: Maximale Listenzahl erreicht</h2>
<div class="sg-object-card-grid" aria-label="VSF List Card Layout maximale Listenzahl erreicht">
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Maximale Listenzahl erreicht Karte">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header">
<div class="sg-strong">Neue Liste anlegen</div>
</header>
<div class="sg-card-segment sg-card-segment--gray sg-object-card__content" data-pattern-part="object-group-card-content">
<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Formular mit Abschnitten">
<form class="sg-form-sections-card" action="#" method="post" aria-label="Neue Liste anlegen Formular deaktiviert">
<p class="sg-vsf-list-card-limit-note">Maximale Listenanzahl erreicht. Du musst eine Liste loeschen oder dein Abonnement upgraden, um mehr Listen anzulegen.</p>
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<section class="sg-form-sections-card__chapter" aria-label="Neue Liste">
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Listentyp</span>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="form" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown--disabled sg-form-disabled sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown ohne aktive Auswahl" data-component-part="pulldown-trigger" data-label-base="Listentyp" disabled aria-disabled="true">
Listentyp
</button>
<div class="sg-pulldown-panel" aria-label="Geoeffnetes Pulldown Listentyp" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Listentyp Optionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Watchlist</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Portfolio</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Screening</span></li>
</ul>
</div>
</div>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Name</span>
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--disabled sg-form-disabled"
type="text"
placeholder="Name eingeben"
aria-label="Name"
maxlength="80"
disabled
>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Beschreibung</span>
<textarea
class="sg-input-multi-line sg-form-disabled"
rows="4"
placeholder="Beschreibung eingeben"
aria-label="Beschreibung"
maxlength="350"
disabled
></textarea>
</label>
</div>
</section>
</div>
<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">
<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">
<button class="sg-interaction-element sg-button sg-button--inactive sg-form-sections-card__action" type="button" disabled aria-disabled="true">Zuruecksetzen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-button--disabled sg-form-sections-card__action" type="submit" disabled aria-disabled="true">Liste anlegen</button>
</div>
</footer>
</form>
</div>
</div>
</article>
</div>
<h2 class="sg-sub-heading sg-section-h2">Layout 3 - List Card inklusive Lösch- und Editier-Funktion</h2>
<div class="sg-object-card-grid sg-delete-confirmation-pattern sg-delete-confirmation-pattern__stage sg-vsf-list-card-context" data-pattern="overlay-card" data-dialog-open="false" aria-label="VSF List Card Layout List Card">
<article class="sg-card sg-object-card sg-delete-confirmation-pattern__target" data-pattern="object-group-card" aria-label="List Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-group-card-header">
<div class="sg-strong">NAME DER LISTE</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#!" data-overlay-open-dialog="edit">Editieren</a>
<a class="sg-sandwich-menu-link" href="#!" data-overlay-open-dialog="delete">Liste loeschen</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-pattern-part="list-card-placeholder-top">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Medianscore:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-pattern-part="list-card-placeholder-bottom">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__three-column-distributed" aria-label="Company Kennzahlen dreispaltig verteilt" data-pattern-part="company-card-metrics-three-column">
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-left">
PE: <span class="sg-data-table__value">28.8</span>
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-center">
PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span>
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-right">
PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span>
</p>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Median-Subscores:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
</div>
</div>
</div>
<p class="sg-body sg-company-card__summary sg-vsf-list-card__summary">BESCHREIBUNG LISTE</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-pattern-part="list-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Unternehmen ansehen</button>
</div>
</footer>
</article>
<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Liste editieren" role="dialog" aria-modal="true" aria-labelledby="vsf-list-card-inline-edit-title" data-overlay-dialog="edit" hidden>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<p class="sg-body sg-delete-confirmation-pattern__text" id="vsf-list-card-inline-edit-title"><strong>Liste editieren</strong></p>
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Name</span>
<input class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable" type="text" value="NAME DER LISTE" aria-label="Name" data-vsf-edit-input>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Beschreibung</span>
<textarea class="sg-input-multi-line sg-form-inactive-selectable" rows="4" aria-label="Beschreibung" data-vsf-edit-input>BESCHREIBUNG LISTE</textarea>
</label>
</div>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-close>Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-vsf-edit-save>
Speichern
</button>
</div>
</div>
</article>
<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Löschbestätigung" role="dialog" aria-modal="true" aria-labelledby="vsf-list-card-inline-delete-title" data-overlay-dialog="delete" hidden>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<p class="sg-body sg-delete-confirmation-pattern__text" id="vsf-list-card-inline-delete-title"><strong>Möchtest du NAME DER LISTE wirklich löschen?</strong></p>
<p class="sg-body sg-delete-confirmation-pattern__text">Du kannst das nicht rückgängig machen. Bestätige durch Eingabe von <span class="sg-delete-confirmation-pattern__code">DELETE</span>.</p>
<label class="sg-labeled-input-row sg-delete-confirmation-pattern__input-row">
<span class="sg-label">Bestätigung</span>
<input class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable" type="text" placeholder="DELETE" aria-label="Löschbestätigung durch DELETE" data-vsf-delete-confirmation-input>
</label>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-close>Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-vsf-delete-confirmation-submit>
Löschen
</button>
</div>
</div>
</article>
</div>
</section>
<script>
const formCard = document.querySelector('#layout-vsf-list-card .sg-object-card-grid:first-of-type .sg-form-sections-card');
const processButton = formCard?.querySelector('.sg-button--process');
const updateProcessButtonState = () => {
if (!formCard || !processButton) {
return;
}
const hasPulldownSelection = Array.from(formCard.querySelectorAll('[data-pulldown-option]'))
.some((option) => option.getAttribute('aria-checked') === 'true');
const hasTextInput = Array.from(formCard.querySelectorAll('input[type="text"], textarea'))
.some((field) => field.value.trim().length > 0);
const isActive = hasPulldownSelection || hasTextInput;
processButton.disabled = !isActive;
processButton.setAttribute('aria-disabled', String(!isActive));
processButton.classList.toggle('sg-button--process-inactive', !isActive);
};
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
updateProcessButtonState();
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (!pulldownDemo) {
return;
}
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
formCard?.querySelectorAll('input[type="text"], textarea').forEach((field) => {
field.addEventListener('input', updateProcessButtonState);
});
updateProcessButtonState();
const editInputs = document.querySelectorAll('[data-vsf-edit-input]');
const editSaveButton = document.querySelector('[data-vsf-edit-save]');
const deleteDialogInput = document.querySelector('[data-vsf-delete-confirmation-input]');
const deleteDialogSubmitButton = document.querySelector('[data-vsf-delete-confirmation-submit]');
if (editInputs.length > 0 && editSaveButton) {
const updateEditSaveState = () => {
const hasInput = Array.from(editInputs).some((field) => field.value.trim().length > 0);
editSaveButton.disabled = !hasInput;
editSaveButton.setAttribute('aria-disabled', String(!hasInput));
editSaveButton.classList.toggle('sg-button--process-inactive', !hasInput);
};
editInputs.forEach((field) => {
field.addEventListener('input', updateEditSaveState);
});
updateEditSaveState();
}
if (deleteDialogInput && deleteDialogSubmitButton) {
const updateDeleteDialogState = () => {
const isValid = deleteDialogInput.value === 'DELETE';
deleteDialogSubmitButton.disabled = !isValid;
deleteDialogSubmitButton.setAttribute('aria-disabled', String(!isValid));
deleteDialogSubmitButton.classList.toggle('sg-button--process-inactive', !isValid);
};
deleteDialogInput.addEventListener('input', updateDeleteDialogState);
updateDeleteDialogState();
}
const closeStageDialogs = (stage) => {
stage.querySelectorAll('.sg-delete-confirmation-pattern__floating-card').forEach((dialog) => {
dialog.hidden = true;
});
stage.dataset.dialogOpen = 'false';
};
document.querySelectorAll('.sg-delete-confirmation-pattern__stage').forEach((stage) => {
closeStageDialogs(stage);
});
document.querySelectorAll('[data-overlay-open-dialog]').forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const stage = link.closest('.sg-delete-confirmation-pattern__stage');
if (!stage) {
return;
}
const target = link.getAttribute('data-overlay-open-dialog');
const dialog = stage.querySelector(`.sg-delete-confirmation-pattern__floating-card[data-overlay-dialog="${target}"]`);
if (!dialog) {
return;
}
closeStageDialogs(stage);
dialog.hidden = false;
stage.dataset.dialogOpen = 'true';
const menuWrap = link.closest('.sg-sandwich-menu-wrap');
const menuButton = menuWrap?.querySelector('.sg-sandwich-button');
if (menuWrap) {
menuWrap.dataset.open = 'false';
}
if (menuButton) {
menuButton.setAttribute('aria-expanded', 'false');
}
});
});
document.querySelectorAll('[data-overlay-dialog-close], [data-vsf-delete-confirmation-submit], [data-vsf-edit-save]').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
const stage = button.closest('.sg-delete-confirmation-pattern__stage');
if (!stage) {
return;
}
closeStageDialogs(stage);
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
const panel = wrap.querySelector('.sg-sandwich-menu-panel');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
if (!nextState || !panel) {
return;
}
wrap.dataset.align = 'right';
const panelRect = panel.getBoundingClientRect();
if (panelRect.left < 0) {
wrap.dataset.align = 'left';
}
});
});
document.addEventListener('click', (event) => {
if (event.target.closest('.sg-pulldown-demo') || event.target.closest('.sg-sandwich-menu-wrap')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,597 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF List Detailseite</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF List Detailseite</h1>
<section class="sg-vsf-list-detail-page" aria-label="VSF List Detailseite">
<div class="sg-tab-button-group sg-vsf-list-detail-page__mobile-tabs" role="tablist" aria-label="Tasten Navigation gross" data-component="tab-navigation" data-component-size="large">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="true" aria-controls="vsf-list-detail-panel-meldungen" id="vsf-list-detail-tab-meldungen" data-component-part="tab-button" data-vsf-list-detail-tab="meldungen">Meldungen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-list-detail-panel-unternehmen" id="vsf-list-detail-tab-unternehmen" data-component-part="tab-button" data-vsf-list-detail-tab="unternehmen">Unternehmen</button>
</div>
<div class="sg-vsf-list-detail-page__content" data-vsf-list-detail-active="meldungen">
<aside class="sg-vsf-list-detail-page__left-column" aria-label="Benachrichtigungen" role="tabpanel" aria-labelledby="vsf-list-detail-tab-meldungen" id="vsf-list-detail-panel-meldungen" data-vsf-list-detail-panel="meldungen">
<div class="sg-delete-confirmation-pattern sg-vsf-list-detail-page__meldungen-overlay-pattern">
<div class="sg-delete-confirmation-pattern__stage" data-pattern="overlay-card" data-dialog-open="false">
<div class="sg-group-card sg-delete-confirmation-pattern__target" data-component="group-card">
<div class="sg-group-card__header-row">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading">Meldungen</h2>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Meldungsmenü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Meldungsmenü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#!" data-component-part="sandwich-menu-link" data-overlay-open-dialog="meldungen">Meldungen anpassen</a>
</div>
</div>
</div>
<button class="sg-interaction-element sg-button sg-button--active sg-vsf-list-detail-page__mobile-toggle" type="button" data-toggle-target="meldungen" data-collapsed-label="Meldungen einblenden" data-expanded-label="Meldungen ausblenden" aria-expanded="true">
Meldungen ausblenden
</button>
<article class="sg-card sg-vsf-list-detail-page__notification-card" data-component="notification-card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">Details</button>
</div>
</article>
<article class="sg-card sg-vsf-list-detail-page__notification-card" data-component="notification-card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">Details</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-vsf-list-detail-page__notification-card" data-component="notification-card" data-component-context="group-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white" data-component-part="card-header">
<p class="sg-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae velit posuere, posuere mauris eu, tincidunt lorem. Proin gravida sapien in mattis molestie. Sed non risus augue. Fusce sed odio vitae purus porta efficitur. Integer tempor congue sem, a convallis lorem ornare eget. Nam. Aenean.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">Details</button>
</div>
</article>
</div>
<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Meldungen anpassen" role="dialog" aria-modal="true" aria-labelledby="vsf-meldungen-overlay-title" data-overlay-dialog="meldungen" hidden>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<p class="sg-body sg-delete-confirmation-pattern__text" id="vsf-meldungen-overlay-title"><strong>Wähle, welche Meldungen angezeigt werden sollen</strong></p>
<section class="sg-form-sections-card__chapter" aria-labelledby="vsf-meldungen-kapitel-1">
<h2 id="vsf-meldungen-kapitel-1" class="sg-strong sg-form-sections-card__chapter-title">Meldungen</h2>
<div class="sg-form-sections-card__option-group" aria-label="Meldungsarten">
<label class="sg-checkbox-field-option sg-body" data-component="checkbox-field" data-component-state="inactive-selectable">
<button class="sg-checkbox-field sg-checkbox-field--inactive-selectable" type="button" role="checkbox" aria-checked="false" aria-label="Meldungsart 1" data-vsf-meldungen-checkbox>
<span class="sg-checkbox-field__mark" aria-hidden="true"></span>
</button>
<span>Meldungsart 1</span>
</label>
<label class="sg-checkbox-field-option sg-body" data-component="checkbox-field" data-component-state="inactive-selectable">
<button class="sg-checkbox-field sg-checkbox-field--inactive-selectable" type="button" role="checkbox" aria-checked="false" aria-label="Meldungsart 2" data-vsf-meldungen-checkbox>
<span class="sg-checkbox-field__mark" aria-hidden="true"></span>
</button>
<span>Meldungsart 2</span>
</label>
<label class="sg-checkbox-field-option sg-body" data-component="checkbox-field" data-component-state="inactive-selectable">
<button class="sg-checkbox-field sg-checkbox-field--inactive-selectable" type="button" role="checkbox" aria-checked="false" aria-label="Meldungsart 3" data-vsf-meldungen-checkbox>
<span class="sg-checkbox-field__mark" aria-hidden="true"></span>
</button>
<span>Meldungsart 3</span>
</label>
</div>
</section>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-close>Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-overlay-dialog-close data-vsf-meldungen-save>Speichern</button>
</div>
</div>
</article>
</div>
</div>
</aside>
<div class="sg-vsf-list-detail-page__right-column" aria-label="Objektkarten" role="tabpanel" aria-labelledby="vsf-list-detail-tab-unternehmen" id="vsf-list-detail-panel-unternehmen" data-vsf-list-detail-panel="unternehmen">
<div class="sg-group-card" data-component="group-card">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading">Unternehmen</h2>
<div class="sg-object-card-grid" aria-label="Pattern Object Card Liste">
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Alcon Inc.</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
<a class="sg-sandwich-menu-link" href="#">Menüpunkt</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button>
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
<article class="sg-card sg-object-card" data-pattern="object-card" aria-label="Objekt Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header"><div class="sg-strong">Alcon Inc.</div><div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small"><button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger"><span class="sg-sandwich-button__icon" aria-hidden="true"><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span><span class="sg-sandwich-button__line"></span></span></button><div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel"><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a><a class="sg-sandwich-menu-link" href="#">Menüpunkt</a></div></div></header>
<div class="sg-card-segment sg-card-segment--body sg-object-card__content" data-pattern-part="object-card-content">
<p class="sg-body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cill.</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-object-card__actions-segment" data-pattern-part="object-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Peer-Group</button><button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Fundamentalanalyse</button>
</div>
</footer>
</article>
</div>
</div>
</div>
</div>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
const updateObjectCardGridRowState = () => {
document.querySelectorAll('.sg-object-card-grid').forEach((grid) => {
const cards = Array.from(grid.querySelectorAll('.sg-object-card'));
grid.classList.remove('sg-object-card-grid--multi-row');
grid.style.removeProperty('--layout-object-card-shared-width');
if (cards.length <= 1) {
return;
}
const firstTop = cards[0].offsetTop;
const hasMultipleRows = cards.some((card) => card.offsetTop !== firstTop);
if (!hasMultipleRows) {
return;
}
const firstRowCards = cards.filter((card) => card.offsetTop === firstTop);
const referenceCard = firstRowCards[0];
if (!referenceCard) {
return;
}
const referenceWidth = referenceCard.getBoundingClientRect().width;
grid.style.setProperty('--layout-object-card-shared-width', `${referenceWidth}px`);
grid.classList.add('sg-object-card-grid--multi-row');
});
};
const vsfListDetailPage = document.querySelector('.sg-vsf-list-detail-page');
const vsfListDetailMobileTabs = document.querySelector('.sg-vsf-list-detail-page__mobile-tabs');
const vsfListDetailButtons = vsfListDetailMobileTabs ? Array.from(vsfListDetailMobileTabs.querySelectorAll('.sg-tab-button')) : [];
const vsfListDetailPanels = {
meldungen: document.querySelector('[data-vsf-list-detail-panel="meldungen"]'),
unternehmen: document.querySelector('[data-vsf-list-detail-panel="unternehmen"]'),
};
const vsfListDetailMobileQuery = window.matchMedia('(max-width: 767px)');
const syncVsfListDetailMobileState = (activeKey) => {
const nextActiveKey = activeKey === 'unternehmen' ? 'unternehmen' : 'meldungen';
if (vsfListDetailPage) {
vsfListDetailPage.dataset.vsfListDetailActive = nextActiveKey;
}
vsfListDetailButtons.forEach((button) => {
const isActive = button.dataset.vsfListDetailTab === nextActiveKey;
button.setAttribute('aria-selected', String(isActive));
button.dataset.componentState = isActive ? 'active' : 'inactive';
});
const isMobile = vsfListDetailMobileQuery.matches;
Object.entries(vsfListDetailPanels).forEach(([key, panel]) => {
if (!panel) {
return;
}
panel.hidden = isMobile && key !== nextActiveKey;
panel.setAttribute('aria-hidden', String(isMobile && key !== nextActiveKey));
});
};
if (vsfListDetailMobileTabs) {
vsfListDetailButtons.forEach((button) => {
button.addEventListener('click', () => {
syncVsfListDetailMobileState(button.dataset.vsfListDetailTab || 'meldungen');
});
});
}
const handleVsfListDetailMobileModeChange = () => {
syncVsfListDetailMobileState(vsfListDetailPage?.dataset.vsfListDetailActive || 'meldungen');
};
if (typeof vsfListDetailMobileQuery.addEventListener === 'function') {
vsfListDetailMobileQuery.addEventListener('change', handleVsfListDetailMobileModeChange);
} else if (typeof vsfListDetailMobileQuery.addListener === 'function') {
vsfListDetailMobileQuery.addListener(handleVsfListDetailMobileModeChange);
}
syncVsfListDetailMobileState(vsfListDetailPage?.dataset.vsfListDetailActive || 'meldungen');
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
if (!button) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
document.querySelectorAll('[data-toggle-target="meldungen"]').forEach((button) => {
button.addEventListener('click', () => {
const groupCard = button.closest('.sg-group-card');
if (!groupCard) {
return;
}
const cards = groupCard.querySelectorAll('.sg-vsf-list-detail-page__notification-card');
const isExpanded = button.getAttribute('aria-expanded') === 'true';
const nextExpanded = !isExpanded;
cards.forEach((card) => {
card.classList.toggle('sg-vsf-list-detail-page__notification-card--hidden', !nextExpanded);
card.setAttribute('aria-hidden', String(!nextExpanded));
});
button.setAttribute('aria-expanded', String(nextExpanded));
button.textContent = nextExpanded ? (button.dataset.expandedLabel || 'Meldungen ausblenden') : (button.dataset.collapsedLabel || 'Meldungen einblenden');
});
});
const closeVsfDialog = (stage) => {
if (!stage) {
return;
}
stage.querySelectorAll('[data-overlay-dialog]').forEach((dialog) => {
dialog.hidden = true;
});
stage.dataset.dialogOpen = 'false';
};
document.querySelectorAll('[data-overlay-open-dialog]').forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const stage = link.closest('.sg-delete-confirmation-pattern__stage');
if (!stage) {
return;
}
const target = link.getAttribute('data-overlay-open-dialog');
const dialog = stage.querySelector(`[data-overlay-dialog="${target}"]`);
if (!dialog) {
return;
}
stage.querySelectorAll('[data-overlay-dialog]').forEach((otherDialog) => {
otherDialog.hidden = true;
});
dialog.hidden = false;
stage.dataset.dialogOpen = 'true';
const menuWrap = link.closest('.sg-sandwich-menu-wrap');
const menuButton = menuWrap?.querySelector('.sg-sandwich-button');
if (menuWrap) {
menuWrap.dataset.open = 'false';
}
if (menuButton) {
menuButton.setAttribute('aria-expanded', 'false');
}
});
});
document.querySelectorAll('[data-overlay-dialog-close]').forEach((button) => {
button.addEventListener('click', () => {
const stage = button.closest('.sg-delete-confirmation-pattern__stage');
closeVsfDialog(stage);
});
});
const meldungenSaveButton = document.querySelector('[data-vsf-meldungen-save]');
const meldungenCheckboxes = document.querySelectorAll('[data-vsf-meldungen-checkbox]');
const updateMeldungenSaveState = () => {
if (!meldungenSaveButton) {
return;
}
const hasSelection = Array.from(meldungenCheckboxes).some((checkbox) => checkbox.getAttribute('aria-checked') === 'true');
meldungenSaveButton.disabled = !hasSelection;
meldungenSaveButton.setAttribute('aria-disabled', String(!hasSelection));
meldungenSaveButton.classList.toggle('sg-button--process-inactive', !hasSelection);
};
meldungenCheckboxes.forEach((checkbox) => {
checkbox.addEventListener('click', () => {
if (checkbox.disabled) {
return;
}
const option = checkbox.closest('[data-component="checkbox-field"]');
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
checkbox.classList.toggle('sg-form-active', nextState);
checkbox.classList.toggle('sg-checkbox-field--inactive-selectable', !nextState);
if (option) {
option.setAttribute('data-component-state', nextState ? 'active' : 'inactive-selectable');
}
updateMeldungenSaveState();
});
});
updateMeldungenSaveState();
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const triggerRect = trigger.getBoundingClientRect();
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.width > triggerRect.width || panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
if (!input || !clearButton) {
return;
}
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
if (!event.target.closest('.sg-pulldown-demo')) {
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
}
if (!event.target.closest('.sg-delete-confirmation-pattern__floating-card') && !event.target.closest('[data-overlay-open-dialog]')) {
document.querySelectorAll('.sg-delete-confirmation-pattern__stage').forEach((stage) => {
if (stage.dataset.dialogOpen === 'true') {
closeVsfDialog(stage);
}
});
}
});
window.addEventListener('load', updateObjectCardGridRowState);
window.addEventListener('resize', updateObjectCardGridRowState);
</script>
</body>
</html>
@@ -0,0 +1,656 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF Listen Übersicht Seite V2</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF Listen Übersicht Seite V2</h1>
<section class="sg-vsf-list-overview-page-v2" aria-label="VSF Listen Übersicht Seite V2">
<article class="sg-portal-header-pattern-variant" aria-label="Portal Header mit Options Row">
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="default" data-component-context="portal-header" data-pattern-part="portal-header-action">
<button class="sg-interaction-element sg-sandwich-button" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Admin</a>
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Logout</a>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Titel</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Gruppen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Listen</button>
</nav>
</div>
</header>
<div class="sg-options-row" aria-label="Optionszeile" data-pattern="options-row">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Sortierung" data-component-part="pulldown-trigger" data-label-base="Sortierung">
Sortierung
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Sortierung" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Sortierungsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 1</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 2</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 3</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 4</span></li>
<li class="sg-pulldown-option sg-pulldown-option--disabled"><span>Menüpunkt 5</span></li>
</ul>
</div>
</div>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Region" data-component-part="pulldown-trigger" data-label-base="Region">
Region
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Region" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Regionsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 1</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 2</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 3</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Menüpunkt 4</span></li>
<li class="sg-pulldown-option sg-pulldown-option--disabled"><span>Menüpunkt 5</span></li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input class="sg-interaction-element sg-input-single-line" type="text" placeholder="Suche" aria-label="Suche">
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
<div class="sg-options-row__right" data-pattern-part="options-row-secondary-actions">
<button class="sg-mode-toggle" type="button" data-active="relative" aria-label="Modus Schieber global: relativ aktiv" data-component="mode-toggle" data-component-context="options-row">
<span class="sg-mode-toggle__label" data-component-part="toggle-label">absolut</span>
<span class="sg-mode-toggle__switch" aria-hidden="true" data-component-part="toggle-track">
<span class="sg-mode-toggle__handle" data-component-part="toggle-handle"></span>
</span>
<span class="sg-mode-toggle__label" data-component-part="toggle-label">relativ</span>
</button>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="options-row">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.
</span>
</span>
</div>
</div>
</article>
<div class="sg-transparent-card sg-vsf-list-overview-page-v2__intro" aria-label="Intro-Bereich" data-component="transparent-card">
<h1 class="sg-heading-h1">Meine Listen</h1>
<div class="sg-text-layout-pattern" data-pattern="text-layout-sechzig-prozent" aria-label="Text Layout 60 Prozent Breite">
<p class="sg-body sg-text-layout-pattern__sample sg-text-layout-pattern__sample--sixty-width" data-pattern-part="text-block-sixty-width">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut a
</p>
</div>
</div>
<div class="sg-object-card-grid" aria-label="VSF Listen Cards Übersicht">
<div class="sg-object-card sg-delete-confirmation-pattern sg-delete-confirmation-pattern__stage sg-delete-confirmation-pattern__host sg-vsf-list-card-context" data-pattern="object-group-card" data-dialog-open="false" aria-label="VSF List Card Layout List Card">
<article class="sg-card sg-delete-confirmation-pattern__target" data-pattern="object-group-card" aria-label="List Card">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkbrown sg-object-card__header" data-pattern-part="object-group-card-header">
<div class="sg-strong">NAME DER LISTE</div>
<div class="sg-sandwich-menu-wrap" data-open="false" data-align="right" data-component="sandwich-menu" data-component-size="small">
<button class="sg-interaction-element sg-sandwich-button sg-sandwich-button--small" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#!" data-overlay-open-dialog="edit">Editieren</a>
<a class="sg-sandwich-menu-link" href="#!" data-overlay-open-dialog="delete">Liste loeschen</a>
</div>
</div>
</header>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-pattern-part="list-card-company-count">
<p class="sg-body"><strong>7 Unternehmen</strong></p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-pattern-part="list-card-placeholder-top">
<div class="sg-score-bar-list sg-score-bar-list--single-score" aria-label="Gesamtscore-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Medianscore:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" role="img" aria-label="Gesamtscore 96 Prozent mit Median-Marker bei 50 Prozent" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
</div>
<p class="sg-bar-label sg-score-state--positive" data-component-part="score-state">attraktiv</p>
</div>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-pattern-part="list-card-placeholder-bottom">
<div class="sg-text-layout-pattern__sample sg-text-layout-pattern__three-column-distributed" aria-label="Company Kennzahlen dreispaltig verteilt" data-pattern-part="company-card-metrics-three-column">
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-left">
PE: <span class="sg-data-table__value">28.8</span>
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-center">
PE forw.: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">23.3</span>
</p>
<p class="sg-body sg-text-layout-pattern__column sg-text-layout-pattern__column--align-right">
PEG: <span class="sg-data-table__value sg-company-card__metric-negative" data-component-state="negative">3.54</span>
</p>
</div>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white sg-object-card__content" data-pattern-part="company-card-content">
<p class="sg-table-label sg-company-card__analysis-title">Median-Subscores:</p>
<div class="sg-score-bar-list sg-company-card__analysis-bars" aria-label="Fundamentalanalyse Score-Balken" data-component="score-bar-list">
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Marktbewertung:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--positive sg-score-bar__value--w96" data-component-part="score-value" data-component-state="positive"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Wachstum:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Profitabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--negative sg-score-bar__value--w35" data-component-part="score-value" data-component-state="negative"></div>
</div>
</div>
<div class="sg-score-bar-item" data-component="score-bar">
<p class="sg-score-bar-label sg-bar-label" data-component-part="score-label">Stabilität:</p>
<div class="sg-score-bar sg-score-bar--marker-mid" data-component-part="score-track">
<div class="sg-score-bar__value sg-score-bar__value--neutral sg-score-bar__value--w64" data-component-part="score-value" data-component-state="neutral"></div>
</div>
</div>
</div>
<p class="sg-body sg-company-card__summary sg-vsf-list-card__summary">BESCHREIBUNG LISTE</p>
</div>
<footer class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-pattern-part="list-card-actions">
<div class="sg-object-card__actions">
<button class="sg-interaction-element sg-button sg-button--active sg-object-card__action" type="button">Unternehmen ansehen</button>
</div>
</footer>
</article>
<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Liste editieren" role="dialog" aria-modal="true" aria-labelledby="vsf-list-card-inline-edit-title" data-overlay-dialog="edit" hidden>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<p class="sg-body sg-delete-confirmation-pattern__text" id="vsf-list-card-inline-edit-title"><strong>Liste editieren</strong></p>
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Name</span>
<input class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable" type="text" value="NAME DER LISTE" aria-label="Name" data-vsf-edit-input>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Beschreibung</span>
<textarea class="sg-input-multi-line sg-form-inactive-selectable" rows="4" aria-label="Beschreibung" data-vsf-edit-input>BESCHREIBUNG LISTE</textarea>
</label>
</div>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-close>Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-vsf-edit-save>
Speichern
</button>
</div>
</div>
</article>
<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Löschbestätigung" role="dialog" aria-modal="true" aria-labelledby="vsf-list-card-inline-delete-title" data-overlay-dialog="delete" hidden>
<div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body">
<p class="sg-body sg-delete-confirmation-pattern__text" id="vsf-list-card-inline-delete-title"><strong>Möchtest du NAME DER LISTE wirklich löschen?</strong></p>
<p class="sg-body sg-delete-confirmation-pattern__text">Du kannst das nicht rückgängig machen. Bestätige durch Eingabe von <span class="sg-delete-confirmation-pattern__code">DELETE</span>.</p>
<label class="sg-labeled-input-row sg-delete-confirmation-pattern__input-row">
<span class="sg-label">Bestätigung</span>
<input class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable" type="text" placeholder="DELETE" aria-label="Löschbestätigung durch DELETE" data-vsf-delete-confirmation-input>
</label>
<div class="sg-delete-confirmation-pattern__actions">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-overlay-dialog-close>Abbrechen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-vsf-delete-confirmation-submit>
Löschen
</button>
</div>
</div>
</article>
</div>
<article class="sg-card sg-object-card" data-pattern="object-group-card" aria-label="Neue Liste anlegen Karte">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkbrown sg-object-card__header" data-pattern-part="object-group-card-header">
<div class="sg-strong">Neue Liste anlegen</div>
</header>
<div class="sg-card-segment sg-card-segment--gray sg-object-card__content" data-pattern-part="object-group-card-content">
<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Formular mit Abschnitten">
<form class="sg-form-sections-card" action="#" method="post" aria-label="Neue Liste anlegen Formular">
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<section class="sg-form-sections-card__chapter" aria-label="Neue Liste">
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Listentyp</span>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="form" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown--inactive-selectable sg-form-inactive-selectable sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown ohne aktive Auswahl" data-component-part="pulldown-trigger" data-label-base="Listentyp">
Listentyp
</button>
<div class="sg-pulldown-panel" aria-label="Geoeffnetes Pulldown Listentyp" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Listentyp Optionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Watchlist</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Portfolio</span></li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option><span>Screening</span></li>
</ul>
</div>
</div>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Name</span>
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
placeholder="Name eingeben"
aria-label="Name"
maxlength="80"
>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Beschreibung</span>
<textarea
class="sg-input-multi-line sg-form-inactive-selectable"
rows="4"
placeholder="Beschreibung eingeben"
aria-label="Beschreibung"
maxlength="350"
></textarea>
</label>
</div>
</section>
</div>
<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">
<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">
<button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button">Zuruecksetzen</button>
<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-form-sections-card__action" type="submit" disabled aria-disabled="true">Liste anlegen</button>
</div>
</footer>
</form>
</div>
</div>
</article>
</div>
<main class="sg-vsf-list-overview-page-v2__content" aria-label="Inhaltsbereich">
<section class="sg-vsf-list-overview-page-v2__primary" aria-label="Primärer Bereich"></section>
<aside class="sg-vsf-list-overview-page-v2__secondary" aria-label="Sekundärer Bereich"></aside>
</main>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-checkbox-field').forEach((checkbox) => {
checkbox.addEventListener('click', (event) => {
event.stopPropagation();
if (checkbox.disabled) {
return;
}
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
const pulldownDemo = checkbox.closest('.sg-pulldown-demo');
if (pulldownDemo) {
updatePulldownSelectionState(pulldownDemo);
pulldownDemo.dataset.open = 'true';
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
const selectionMode = pulldownDemo.dataset.selectionMode || 'single';
if (selectionMode === 'multiple') {
const nextState = option.getAttribute('aria-checked') !== 'true';
option.setAttribute('aria-checked', String(nextState));
} else {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
}
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (selectionMode === 'multiple') {
pulldownDemo.dataset.open = 'true';
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
} else {
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
if (!input || !clearButton) {
return;
}
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
if (event.target.closest('.sg-pulldown-demo')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
const editInputs = document.querySelectorAll('[data-vsf-edit-input]');
const editSaveButton = document.querySelector('[data-vsf-edit-save]');
const deleteDialogInput = document.querySelector('[data-vsf-delete-confirmation-input]');
const deleteDialogSubmitButton = document.querySelector('[data-vsf-delete-confirmation-submit]');
if (editInputs.length > 0 && editSaveButton) {
const updateEditSaveState = () => {
const hasInput = Array.from(editInputs).some((field) => field.value.trim().length > 0);
editSaveButton.disabled = !hasInput;
editSaveButton.setAttribute('aria-disabled', String(!hasInput));
editSaveButton.classList.toggle('sg-button--process-inactive', !hasInput);
};
editInputs.forEach((field) => {
field.addEventListener('input', updateEditSaveState);
});
updateEditSaveState();
}
if (deleteDialogInput && deleteDialogSubmitButton) {
const updateDeleteDialogState = () => {
const isValid = deleteDialogInput.value === 'DELETE';
deleteDialogSubmitButton.disabled = !isValid;
deleteDialogSubmitButton.setAttribute('aria-disabled', String(!isValid));
deleteDialogSubmitButton.classList.toggle('sg-button--process-inactive', !isValid);
};
deleteDialogInput.addEventListener('input', updateDeleteDialogState);
updateDeleteDialogState();
}
const closeStageDialogs = (stage) => {
stage.querySelectorAll('.sg-delete-confirmation-pattern__floating-card').forEach((dialog) => {
dialog.hidden = true;
});
stage.dataset.dialogOpen = 'false';
};
document.querySelectorAll('.sg-delete-confirmation-pattern__stage').forEach((stage) => {
closeStageDialogs(stage);
});
document.querySelectorAll('[data-overlay-open-dialog]').forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const stage = link.closest('.sg-delete-confirmation-pattern__stage');
if (!stage) {
return;
}
const target = link.getAttribute('data-overlay-open-dialog');
const dialog = stage.querySelector(`.sg-delete-confirmation-pattern__floating-card[data-overlay-dialog="${target}"]`);
if (!dialog) {
return;
}
closeStageDialogs(stage);
dialog.hidden = false;
stage.dataset.dialogOpen = 'true';
const menuWrap = link.closest('.sg-sandwich-menu-wrap');
const menuButton = menuWrap?.querySelector('.sg-sandwich-button');
if (menuWrap) {
menuWrap.dataset.open = 'false';
}
if (menuButton) {
menuButton.setAttribute('aria-expanded', 'false');
}
});
});
document.querySelectorAll('[data-overlay-dialog-close], [data-vsf-delete-confirmation-submit], [data-vsf-edit-save]').forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
const stage = button.closest('.sg-delete-confirmation-pattern__stage');
if (!stage) {
return;
}
closeStageDialogs(stage);
});
});
const createListForm = document.querySelector('.sg-vsf-list-overview-page-v2 .sg-object-card-grid .sg-form-sections-card');
const createListProcessButton = createListForm?.querySelector('.sg-button--process');
const updateCreateListProcessButtonState = () => {
if (!createListForm || !createListProcessButton) {
return;
}
const hasPulldownSelection = Array.from(createListForm.querySelectorAll('[data-pulldown-option]'))
.some((option) => option.getAttribute('aria-checked') === 'true');
const hasTextInput = Array.from(createListForm.querySelectorAll('input[type="text"], textarea'))
.some((field) => field.value.trim().length > 0);
const isActive = hasPulldownSelection || hasTextInput;
createListProcessButton.disabled = !isActive;
createListProcessButton.setAttribute('aria-disabled', String(!isActive));
createListProcessButton.classList.toggle('sg-button--process-inactive', !isActive);
};
createListForm?.querySelectorAll('input[type="text"], textarea').forEach((field) => {
field.addEventListener('input', updateCreateListProcessButtonState);
});
createListForm?.querySelectorAll('.sg-pulldown-demo__trigger, [data-pulldown-option]').forEach((control) => {
control.addEventListener('click', () => {
setTimeout(updateCreateListProcessButtonState, 0);
});
});
updateCreateListProcessButtonState();
</script>
</body>
</html>
+819
View File
@@ -0,0 +1,819 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide VSF Meldungen</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Layout VSF Meldungen</h1>
<article class="sg-portal-header-pattern-variant" aria-label="Portal Header mit Options Row">
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap sg-sandwich-menu-wrap" data-open="false" data-component="sandwich-menu" data-component-size="default" data-component-context="portal-header" data-pattern-part="portal-header-action">
<button class="sg-interaction-element sg-sandwich-button" type="button" aria-expanded="false" aria-label="Menü öffnen" data-component-part="sandwich-trigger">
<span class="sg-sandwich-button__icon" aria-hidden="true">
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
<span class="sg-sandwich-button__line"></span>
</span>
</button>
<div class="sg-sandwich-menu-panel" aria-label="Ausgeklapptes Menü" data-component-part="sandwich-panel">
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Admin</a>
<a class="sg-sandwich-menu-link" href="#" data-component-part="sandwich-menu-link">Logout</a>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Titel</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Gruppen</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Listen</button>
</nav>
</div>
</header>
<div class="sg-options-row" aria-label="Optionszeile" data-pattern="options-row">
<div class="sg-options-row__left" data-pattern-part="options-row-primary-actions">
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Sortierung" data-component-part="pulldown-trigger" data-label-base="Sortierung">
Sortierung
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Sortierung" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Sortierungsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-pulldown-demo" data-open="false" data-align="left" data-selection-mode="single" data-component="pulldown" data-component-context="options-row" data-component-state="inactive-selectable">
<button class="sg-interaction-element sg-pulldown sg-pulldown-demo__trigger" type="button" aria-expanded="false" aria-label="Pulldown Region" data-component-part="pulldown-trigger" data-label-base="Region">
Region
</button>
<div class="sg-pulldown-panel" aria-label="Geöffnetes Pulldown Region" data-component-part="pulldown-panel">
<ul class="sg-pulldown-option-list" aria-label="Regionsoptionen">
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 1</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 2</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 3</span>
</li>
<li class="sg-pulldown-option" role="checkbox" aria-checked="false" data-pulldown-option>
<span>Menüpunkt 4</span>
</li>
<li class="sg-pulldown-option sg-pulldown-option--disabled">
<span>Menüpunkt 5</span>
</li>
</ul>
</div>
</div>
<div class="sg-search-field-row">
<span class="sg-input-single-line-wrap sg-search-field-input" data-has-value="false" data-component="single-line-input" data-component-context="options-row" data-component-state="inactive-selectable">
<input
class="sg-interaction-element sg-input-single-line"
type="text"
placeholder="Suche"
aria-label="Suche"
>
<button class="sg-input-clear-button" type="button" aria-label="Eingabe löschen" data-component-part="input-clear-button">×</button>
</span>
<span class="sg-search-result-count sg-table-label" aria-live="polite" data-pattern-part="options-row-search-result-count">0 Treffer</span>
</div>
</div>
<div class="sg-options-row__right" data-pattern-part="options-row-secondary-actions">
<button class="sg-mode-toggle" type="button" data-active="relative" aria-label="Modus Schieber global: relativ aktiv" data-component="mode-toggle" data-component-context="options-row">
<span class="sg-mode-toggle__label" data-component-part="toggle-label">absolut</span>
<span class="sg-mode-toggle__switch" aria-hidden="true" data-component-part="toggle-track">
<span class="sg-mode-toggle__handle" data-component-part="toggle-handle"></span>
</span>
<span class="sg-mode-toggle__label" data-component-part="toggle-label">relativ</span>
</button>
<span class="sg-help-icon-wrap" data-open="false" data-align="left" data-component="help-icon" data-component-context="options-row">
<button class="sg-help-icon" type="button" aria-expanded="false" aria-label="Hilfetext anzeigen" data-component-part="help-trigger">?</button>
<span class="sg-help-icon-panel sg-table-label" role="tooltip" data-component-part="help-panel">
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.
</span>
</span>
</div>
</div>
<div class="sg-transparent-card sg-vsf-list-detail-page__intro-block sg-portal-header-pattern-variant__next-element" aria-label="Meldungen Titel" data-component="transparent-card">
<h1 class="sg-heading-h1 sg-vsf-list-detail-page__title">Meldungen</h1>
</div>
<section class="sg-vsf-meldungen-layout" aria-label="VSF Meldungen">
<div class="sg-group-card" data-component="group-card" aria-labelledby="vsf-meldungen-updates-heading">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading" id="vsf-meldungen-updates-heading">Updates</h2>
<div class="sg-preview-area sg-notifications-pattern" aria-label="Updates Meldungen">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Update Signal Eins</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Aktualisierung mit hoher Priorität zur Illustration.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Update Signal Zwei</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Positive Meldung zur Illustration.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Update Signal Drei</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Weitere Meldung zur Illustration.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
</div>
<div class="sg-navigation-card-layout sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
<div class="sg-group-card" data-component="group-card" aria-labelledby="vsf-meldungen-kaufsignale-heading">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading" id="vsf-meldungen-kaufsignale-heading">Kaufsignale</h2>
<div class="sg-preview-area sg-notifications-pattern" aria-label="Kaufsignale Meldungen">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Signal Eins</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Verkaufssignal zur Illustration.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Signal Zwei</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Positives Kaufsignal zur Illustration.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Kaufsignal Signal Drei</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Weitere Signalmeldung zur Illustration.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
</div>
<div class="sg-navigation-card-layout sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
<div class="sg-group-card" data-component="group-card" aria-labelledby="vsf-meldungen-termine-heading">
<h2 class="sg-heading-h2 sg-text-on-dark sg-group-card__heading" id="vsf-meldungen-termine-heading">Termine</h2>
<div class="sg-preview-area sg-notifications-pattern" aria-label="Termine Meldungen">
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Termin Signal Eins</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Terminmeldung mit weissem Hintergrund.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Termin Signal Zwei</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Weiterer Termin mit weissem Hintergrund.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body">
<span class="sg-strong">Termin Signal Drei</span>
</p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">
Dritte Terminmeldung mit weissem Hintergrund.
</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
</div>
<div class="sg-navigation-card-layout sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
</section>
<section class="sg-vsf-meldungen-mobile" aria-label="VSF Meldungen für Mobile">
<div class="sg-group-card" data-component="group-card">
<div class="sg-tab-button-group sg-vsf-meldungen-mobile__tabs" role="tablist" aria-label="Tasten Navigation gross" data-component="tab-navigation" data-component-size="large">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="true" aria-controls="vsf-meldungen-mobile-panel-updates" id="vsf-meldungen-mobile-tab-updates" data-component-part="tab-button">Updates</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-meldungen-mobile-panel-kaufsignale" id="vsf-meldungen-mobile-tab-kaufsignale" data-component-part="tab-button">Kaufsignale</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" aria-controls="vsf-meldungen-mobile-panel-termine" id="vsf-meldungen-mobile-tab-termine" data-component-part="tab-button">Termine</button>
</div>
<div class="sg-vsf-meldungen-mobile__panels">
<div class="sg-vsf-meldungen-mobile__panel" id="vsf-meldungen-mobile-panel-updates" role="tabpanel" aria-labelledby="vsf-meldungen-mobile-tab-updates">
<div class="sg-preview-area sg-notifications-pattern" aria-label="Updates Meldungen Mobile">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Update Signal Eins</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Aktualisierung mit hoher Priorität zur Illustration.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Update Signal Zwei</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Positive Meldung zur Illustration.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Update Signal Drei</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Weitere Meldung zur Illustration.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
</div>
<div class="sg-navigation-card-layout sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
<div class="sg-vsf-meldungen-mobile__panel" id="vsf-meldungen-mobile-panel-kaufsignale" role="tabpanel" aria-labelledby="vsf-meldungen-mobile-tab-kaufsignale" hidden>
<div class="sg-preview-area sg-notifications-pattern" aria-label="Kaufsignale Meldungen Mobile">
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Kaufsignal Signal Eins</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-red sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Verkaufssignal zur Illustration.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-red" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Kaufsignal Signal Zwei</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-green sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Positives Kaufsignal zur Illustration.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-green" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Kaufsignal Signal Drei</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--signal-yellow sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Weitere Signalmeldung zur Illustration.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--signal-yellow" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
</div>
<div class="sg-navigation-card-layout sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
<div class="sg-vsf-meldungen-mobile__panel" id="vsf-meldungen-mobile-panel-termine" role="tabpanel" aria-labelledby="vsf-meldungen-mobile-tab-termine" hidden>
<div class="sg-preview-area sg-notifications-pattern" aria-label="Termine Meldungen Mobile">
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Termin Signal Eins</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Terminmeldung mit weissem Hintergrund.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Termin Signal Zwei</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Weiterer Termin mit weissem Hintergrund.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
<article class="sg-card sg-card--notification-white sg-notifications-pattern__card" data-pattern="notification-with-title" data-component="notification-card">
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment sg-notifications-pattern__title-segment" data-component-part="card-header">
<p class="sg-body"><span class="sg-strong">Termin Signal Drei</span></p>
</div>
<div class="sg-card-segment sg-card-segment--header sg-card-segment--white sg-notifications-pattern__text-segment" data-component-part="card-header">
<p class="sg-body">Dritte Terminmeldung mit weissem Hintergrund.</p>
</div>
<div class="sg-card-segment sg-card-segment--body sg-card-segment--white" data-component-part="card-body">
<button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">zum Unternehmen</button>
</div>
</article>
</div>
<div class="sg-navigation-card-layout sg-navigation-card-block">
<article class="sg-card" data-component="card" data-pattern="navigation-card" aria-label="Navigations-Card">
<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-pattern-part="navigation-card-segment">
<div class="sg-navigation-card-center">
<a class="sg-hyperlink" href="#" data-component="hyperlink">Mehr laden</a>
</div>
</div>
</article>
</div>
</div>
</div>
</div>
</section>
</article>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const triggerRect = trigger.getBoundingClientRect();
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.width > triggerRect.width || panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-checkbox-field').forEach((checkbox) => {
checkbox.addEventListener('click', (event) => {
event.stopPropagation();
if (checkbox.disabled) {
return;
}
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
const pulldownDemo = checkbox.closest('.sg-pulldown-demo');
if (pulldownDemo) {
updatePulldownSelectionState(pulldownDemo);
pulldownDemo.dataset.open = 'true';
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
const selectionMode = pulldownDemo.dataset.selectionMode || 'single';
if (selectionMode === 'multiple') {
const nextState = option.getAttribute('aria-checked') !== 'true';
option.setAttribute('aria-checked', String(nextState));
} else {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
}
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (selectionMode === 'multiple') {
pulldownDemo.dataset.open = 'true';
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
} else {
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
if (!input || !clearButton) {
return;
}
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
if (event.target.closest('.sg-pulldown-demo')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
</script>
<script>
const updateNotificationsPatternRowState = () => {
document.querySelectorAll('.sg-notifications-pattern').forEach((grid) => {
const cards = Array.from(grid.querySelectorAll('.sg-notifications-pattern__card'));
grid.classList.remove('sg-notifications-pattern--multi-row');
grid.style.removeProperty('--layout-object-card-shared-width');
if (cards.length <= 1) {
return;
}
const firstTop = cards[0].offsetTop;
const hasMultipleRows = cards.some((card) => card.offsetTop !== firstTop);
if (!hasMultipleRows) {
return;
}
const firstRowCards = cards.filter((card) => card.offsetTop === firstTop);
const referenceCard = firstRowCards[0];
if (!referenceCard) {
return;
}
const referenceWidth = referenceCard.getBoundingClientRect().width;
grid.style.setProperty('--layout-object-card-shared-width', `${referenceWidth}px`);
grid.classList.add('sg-notifications-pattern--multi-row');
});
};
window.addEventListener('load', updateNotificationsPatternRowState);
window.addEventListener('resize', updateNotificationsPatternRowState);
const mobileVsfMeldungen = document.querySelector('.sg-vsf-meldungen-mobile');
if (mobileVsfMeldungen) {
const mobileTabs = Array.from(mobileVsfMeldungen.querySelectorAll('[role="tab"]'));
const mobilePanels = Array.from(mobileVsfMeldungen.querySelectorAll('[role="tabpanel"]'));
const activateMobileTab = (targetTab) => {
mobileTabs.forEach((tab) => {
tab.setAttribute('aria-selected', String(tab === targetTab));
tab.dataset.componentState = tab === targetTab ? 'active' : 'inactive';
});
mobilePanels.forEach((panel) => {
panel.hidden = panel.id !== targetTab.getAttribute('aria-controls');
});
};
mobileTabs.forEach((tab) => {
tab.addEventListener('click', () => {
activateMobileTab(tab);
});
});
const initialTab = mobileTabs.find((tab) => tab.getAttribute('aria-selected') === 'true') || mobileTabs[0];
if (initialTab) {
activateMobileTab(initialTab);
}
}
</script>
</body>
</html>
@@ -0,0 +1,283 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Pattern VSF Portal Header Public</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Pattern VSF Portal Header Public</h1>
<section id="pattern-portal-header">
<article class="sg-portal-header-pattern-variant" aria-label="Portal Header ohne Options Row">
<header class="sg-portal-header" aria-label="Portal Header" data-pattern="portal-header">
<div class="sg-portal-header__main" data-pattern-part="portal-header-main">
<p class="sg-portal-header__brand sg-brand-title" data-pattern-part="portal-header-brand">ValueStockFinder</p>
<div class="sg-portal-header__menu-wrap" data-pattern-part="portal-header-action">
<div class="sg-tab-button-group" role="tablist" aria-label="Anmeldung" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">
Login
</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" role="tab" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">
Registrieren
</button>
</div>
</div>
<nav class="sg-portal-header__tabs sg-tab-button-group" aria-label="Hauptnavigation" data-component="tab-navigation" data-component-size="large" data-component-context="portal-header" data-pattern-part="portal-header-navigation">
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="true" data-component-part="tab-button" data-component-state="active">Übersicht</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Features</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Preise</button>
<button class="sg-interaction-element sg-button sg-tab-button" type="button" aria-selected="false" data-component-part="tab-button" data-component-state="inactive">Firma der Woche</button>
</nav>
</div>
</header>
<div class="sg-transparent-card sg-portal-header-pattern-variant__next-element" aria-label="Willkommen bei ValueStockFinder" data-component="transparent-card">
<h1 class="sg-heading-h1 sg-text-on-dark">Willkommen bei ValueStockFinder</h1>
</div>
</article>
</section>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs, .sg-portal-header__menu-wrap .sg-tab-button-group').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
document.querySelectorAll('.sg-mode-toggle').forEach((toggle) => {
toggle.addEventListener('click', () => {
const nextState = toggle.dataset.active === 'relative' ? 'absolute' : 'relative';
toggle.dataset.active = nextState;
toggle.dataset.componentState = nextState;
toggle.setAttribute(
'aria-label',
`Modus Schieber global: ${nextState === 'relative' ? 'relativ' : 'absolut'} aktiv`
);
});
});
const updatePulldownSelectionState = (demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
const selectableOptions = demo.querySelectorAll('[data-pulldown-option]');
if (!trigger || selectableOptions.length === 0) {
return;
}
const selectedCount = Array.from(selectableOptions).filter((option) => {
return option.getAttribute('aria-checked') === 'true';
}).length;
selectableOptions.forEach((option) => {
const optionRow = option.closest('.sg-pulldown-option');
if (!optionRow) {
return;
}
optionRow.classList.toggle(
'sg-pulldown-option--selected',
option.getAttribute('aria-checked') === 'true'
);
});
const labelBase = trigger.dataset.labelBase || 'Auswahl';
trigger.textContent = selectedCount > 0 ? `${labelBase} (${selectedCount})` : labelBase;
trigger.classList.toggle('sg-pulldown--selected', selectedCount > 0);
trigger.classList.toggle('sg-form-active', selectedCount > 0);
trigger.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
demo.dataset.componentState = selectedCount > 0 ? 'selected' : 'inactive-selectable';
trigger.setAttribute(
'aria-label',
selectedCount > 0 ? `Pulldown ${labelBase} mit aktiver Auswahl` : `Pulldown ${labelBase} ohne aktive Auswahl`
);
};
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
if (!trigger) {
return;
}
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = demo.dataset.open !== 'true';
document.querySelectorAll('.sg-pulldown-demo').forEach((otherDemo) => {
const otherTrigger = otherDemo.querySelector('.sg-pulldown-demo__trigger');
otherDemo.dataset.open = 'false';
if (otherTrigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
demo.dataset.align = 'left';
demo.dataset.open = String(nextState);
trigger.setAttribute('aria-expanded', String(nextState));
if (!nextState) {
return;
}
const panel = demo.querySelector('.sg-pulldown-panel');
if (!panel) {
return;
}
const panelRect = panel.getBoundingClientRect();
if (panelRect.right > window.innerWidth) {
demo.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
demo.dataset.align = 'left';
}
});
});
document.querySelectorAll('.sg-checkbox-field').forEach((checkbox) => {
checkbox.addEventListener('click', (event) => {
event.stopPropagation();
if (checkbox.disabled) {
return;
}
const nextState = checkbox.getAttribute('aria-checked') !== 'true';
checkbox.setAttribute('aria-checked', String(nextState));
const pulldownDemo = checkbox.closest('.sg-pulldown-demo');
if (pulldownDemo) {
updatePulldownSelectionState(pulldownDemo);
pulldownDemo.dataset.open = 'true';
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
}
});
});
document.querySelectorAll('.sg-pulldown-option[data-pulldown-option]').forEach((option) => {
option.addEventListener('click', (event) => {
event.stopPropagation();
const pulldownDemo = option.closest('.sg-pulldown-demo');
if (pulldownDemo) {
const selectionMode = pulldownDemo.dataset.selectionMode || 'single';
if (selectionMode === 'multiple') {
const nextState = option.getAttribute('aria-checked') !== 'true';
option.setAttribute('aria-checked', String(nextState));
} else {
pulldownDemo.querySelectorAll('[data-pulldown-option]').forEach((otherOption) => {
otherOption.setAttribute('aria-checked', String(otherOption === option));
});
}
updatePulldownSelectionState(pulldownDemo);
const trigger = pulldownDemo.querySelector('.sg-pulldown-demo__trigger');
if (selectionMode === 'multiple') {
pulldownDemo.dataset.open = 'true';
if (trigger) {
trigger.setAttribute('aria-expanded', 'true');
}
} else {
pulldownDemo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
}
});
});
document.querySelectorAll('.sg-pulldown-demo').forEach(updatePulldownSelectionState);
window.sgInitHelpIconOverlays({
closeOnOpenSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
outsideClickIgnoreSelectors: ['.sg-pulldown-demo', '.sg-sandwich-menu-wrap'],
});
document.querySelectorAll('.sg-input-single-line-wrap').forEach((wrap) => {
const input = wrap.querySelector('.sg-input-single-line');
const clearButton = wrap.querySelector('.sg-input-clear-button');
if (!input || !clearButton) {
return;
}
const updateState = () => {
wrap.dataset.hasValue = String(input.value.length > 0);
wrap.dataset.componentState = input.value.length > 0 ? 'active' : 'inactive-selectable';
};
input.addEventListener('input', updateState);
clearButton.addEventListener('click', () => {
input.value = '';
updateState();
input.focus();
});
updateState();
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.sg-sandwich-menu-wrap')) {
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
}
if (event.target.closest('.sg-pulldown-demo')) {
return;
}
document.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,111 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Registriere dich bei ValueStockFinder</title>
<link rel="stylesheet" href="../styleguide.css">
</head>
<body class="sg-vsf-register-step-1-page">
<h1 class="sg-main-heading">Layout VFS Keycloak Login</h1>
<main class="sg-vsf-register-step-1">
<article class="sg-card sg-object-card sg-object-card--variable-height sg-vsf-register-step-1__card" data-pattern="object-card" aria-label="Sign in">
<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header" data-pattern-part="object-card-header">
<div class="sg-strong">Welcome back to ValueStockFinder!</div>
</header>
<footer class="sg-card-segment sg-card-segment--gray" aria-label="Sign in form">
<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Form with sections">
<form class="sg-form-sections-card" action="#" method="post">
<div class="sg-form-sections-card__body" data-pattern-part="form-body">
<h2 class="sg-strong sg-form-sections-card__title">Sign in to your account</h2>
<div class="sg-form-sections-card__field-group">
<label class="sg-labeled-input-row">
<span class="sg-label">Username or email</span>
<span class="sg-input-validation-stack">
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="text"
value="lkn"
aria-label="Username or email"
aria-invalid="true"
aria-describedby="login-identifier-error"
autocomplete="username"
>
<span class="sg-form-validation-text" id="login-identifier-error">Invalid username or password.</span>
</span>
</label>
<label class="sg-labeled-input-row">
<span class="sg-label">Password</span>
<input
class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable"
type="password"
placeholder="Enter password"
aria-label="Password"
aria-invalid="true"
autocomplete="current-password"
>
</label>
</div>
</div>
<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">
<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">
<button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button">Cancel</button>
<button class="sg-interaction-element sg-button sg-button--process sg-form-sections-card__action" type="submit">Sign In</button>
</div>
</footer>
</form>
</div>
</footer>
<footer class="sg-card-segment sg-card-segment--gray" aria-label="Google login from keycoak">
<div class="sg-strong">Or sign in with</div>
<div class="sg-body">google login from keycoak</div>
</footer>
<footer class="sg-card-segment sg-card-segment--gray" aria-label="Already have an account">
<div class="sg-body">
New user? <a class="sg-hyperlink" href="#" data-component="hyperlink">Register</a>.
</div>
</footer>
</article>
</main>
<script src="../scripts/help-icon-overlays.js"></script>
<script>
document.querySelectorAll('.sg-portal-header__tabs, .sg-portal-header__menu-wrap .sg-tab-button-group').forEach((group) => {
group.querySelectorAll('.sg-tab-button').forEach((button) => {
button.addEventListener('click', () => {
group.querySelectorAll('.sg-tab-button').forEach((otherButton) => {
const isActive = otherButton === button;
otherButton.setAttribute('aria-selected', String(isActive));
otherButton.dataset.componentState = isActive ? 'active' : 'inactive';
});
});
});
});
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
document.querySelectorAll('.sg-sandwich-menu-wrap').forEach((otherWrap) => {
const otherButton = otherWrap.querySelector('.sg-sandwich-button');
otherWrap.dataset.open = 'false';
if (otherButton) {
otherButton.setAttribute('aria-expanded', 'false');
}
});
wrap.dataset.open = String(nextState);
button.setAttribute('aria-expanded', String(nextState));
});
});
</script>
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CSS_FILE="$ROOT_DIR/styleguide.css"
selectors=(
"sg-portal-header"
"sg-options-row"
"sg-card-list-page-drawer__header"
"sg-card-list-page-drawer__content"
)
for selector in "${selectors[@]}"; do
block="$(awk -v selector=".$selector" '
$0 ~ selector"[[:space:]]*\\{" {in_block=1}
in_block {print}
in_block && /}/ {exit}
' "$CSS_FILE")"
if [[ -z "$block" ]]; then
echo "ERROR: selector .$selector not found in styleguide.css"
exit 1
fi
if echo "$block" | rg -n "(padding|padding-inline|padding-left|padding-right|inset|inset-inline)([^\n;]*)([0-9]+px)" >/dev/null; then
echo "ERROR: hardcoded px inset/padding value found in .$selector"
echo "$block"
exit 1
fi
done
echo "OK: no hardcoded px inset values in guarded pattern selectors"
@@ -0,0 +1,129 @@
(function initHelpIconOverlayModule() {
const CLOSE_HANDLERS = {
'.sg-pulldown-demo': (root) => {
root.querySelectorAll('.sg-pulldown-demo').forEach((demo) => {
const trigger = demo.querySelector('.sg-pulldown-demo__trigger');
demo.dataset.open = 'false';
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
});
},
'.sg-sandwich-menu-wrap': (root) => {
root.querySelectorAll('.sg-sandwich-menu-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-sandwich-button');
wrap.dataset.open = 'false';
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
},
};
const getViewportWidth = () => {
if (window.visualViewport && typeof window.visualViewport.width === 'number') {
return window.visualViewport.width;
}
return window.innerWidth;
};
const getSafeInsetPx = () => {
const rootStyles = getComputedStyle(document.documentElement);
const spacingSmallRaw = rootStyles.getPropertyValue('--spacing-small').trim();
const rootFontSize = parseFloat(rootStyles.fontSize) || 16;
const spacingSmallValue = parseFloat(spacingSmallRaw);
if (Number.isNaN(spacingSmallValue)) {
return 0;
}
if (spacingSmallRaw.endsWith('rem')) {
return spacingSmallValue * rootFontSize;
}
return spacingSmallValue;
};
const closeAllHelpIcons = (root) => {
root.querySelectorAll('.sg-help-icon-wrap').forEach((wrap) => {
const button = wrap.querySelector('.sg-help-icon');
const panel = wrap.querySelector('.sg-help-icon-panel');
wrap.dataset.open = 'false';
if (panel) {
panel.style.removeProperty('transform');
}
if (button) {
button.setAttribute('aria-expanded', 'false');
}
});
};
window.sgInitHelpIconOverlays = (options = {}) => {
const root = options.root || document;
const closeOnOpenSelectors = options.closeOnOpenSelectors || [];
const outsideClickIgnoreSelectors = options.outsideClickIgnoreSelectors || [];
root.querySelectorAll('.sg-help-icon-wrap').forEach((wrap) => {
if (wrap.dataset.helpIconInit === 'true') {
return;
}
wrap.dataset.helpIconInit = 'true';
const button = wrap.querySelector('.sg-help-icon');
const panel = wrap.querySelector('.sg-help-icon-panel');
if (!button || !panel) {
return;
}
button.addEventListener('click', (event) => {
event.stopPropagation();
const nextState = wrap.dataset.open !== 'true';
closeAllHelpIcons(root);
closeOnOpenSelectors.forEach((selector) => {
const handler = CLOSE_HANDLERS[selector];
if (handler) {
handler(root);
}
});
if (!nextState) {
return;
}
wrap.dataset.align = 'left';
wrap.dataset.open = 'true';
button.setAttribute('aria-expanded', 'true');
const viewportWidth = getViewportWidth();
const panelRect = panel.getBoundingClientRect();
if (panelRect.right > viewportWidth) {
wrap.dataset.align = 'right';
}
const alignedPanelRect = panel.getBoundingClientRect();
if (alignedPanelRect.left < 0) {
wrap.dataset.align = 'left';
}
const clampedRect = panel.getBoundingClientRect();
const safeInset = getSafeInsetPx();
let shiftX = 0;
if (clampedRect.right > (viewportWidth - safeInset)) {
shiftX -= clampedRect.right - (viewportWidth - safeInset);
}
if ((clampedRect.left + shiftX) < safeInset) {
shiftX += safeInset - (clampedRect.left + shiftX);
}
if (shiftX !== 0) {
panel.style.transform = `translateX(${shiftX}px)`;
}
});
});
document.addEventListener('click', (event) => {
const isInsideIgnoredZone = ['.sg-help-icon-wrap', ...outsideClickIgnoreSelectors]
.some((selector) => event.target.closest(selector));
if (isInsideIgnoredZone) {
return;
}
closeAllHelpIcons(root);
});
};
})();
@@ -0,0 +1,220 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
STYLEGUIDE_REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SOURCE_CSS="$STYLEGUIDE_REPO_ROOT/styleguide.css"
VERSION_FILE="$STYLEGUIDE_REPO_ROOT/VERSION"
PORTAL_CSS_REL="public/assets/styleguide.upstream.css"
PORTAL_BUILT_CSS_REL="public/assets/styles.css"
PORTAL_META_REL="public/assets/styleguide.upstream.meta.json"
PORTAL_STYLEGUIDE_DOCS_REL="docs/styleguide"
PORTAL_BUILD_SCRIPT_REL="scripts/styleguide/build_styles.sh"
COMMIT_IN_PORTAL="false"
usage() {
cat <<USAGE
Usage:
$(basename "$0") [--commit-portal]
Options:
--commit-portal Create commit in portal repo after sync.
-h, --help Show this help.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--commit-portal)
COMMIT_IN_PORTAL="true"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage
exit 1
;;
esac
done
if [[ ! -f "$SOURCE_CSS" ]]; then
echo "Source CSS not found: $SOURCE_CSS" >&2
exit 1
fi
if [[ ! -f "$VERSION_FILE" ]]; then
echo "Version file not found: $VERSION_FILE" >&2
exit 1
fi
flatten_css() {
local source_file="$1"
local source_root="$2"
local output_file="$3"
: > "$output_file"
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*@import[[:space:]]+\"([^\"]+)\"[[:space:]]*\;[[:space:]]*$ ]]; then
local import_path="${BASH_REMATCH[1]}"
local import_abs="$source_root/$import_path"
if [[ ! -f "$import_abs" ]]; then
echo "Imported CSS not found: $import_abs" >&2
exit 1
fi
cat "$import_abs" >> "$output_file"
printf "\n" >> "$output_file"
else
printf "%s\n" "$line" >> "$output_file"
fi
done < "$source_file"
}
build_portal_css() {
local portal_key="$1"
local source_file="$2"
local output_file="$3"
case "$portal_key" in
vsf)
awk '
BEGIN { skip=0 }
/^:root\[data-portal="naurua"\] \{/ { skip=1; next }
skip && /^}/ { skip=0; next }
skip { next }
{ print }
' "$source_file" > "$output_file"
;;
naurua)
awk '
BEGIN { in_override=0 }
/^:root\[data-portal="naurua"\] \{/ { print ":root {"; in_override=1; next }
in_override && /^}/ { print "}"; in_override=0; next }
{ print }
' "$source_file" > "$output_file"
;;
*)
echo "Unknown portal: $portal_key" >&2
exit 1
;;
esac
}
sync_portal() {
local portal_key="$1"
local portal_repo_path="$2"
local portal_css_path="$portal_repo_path/$PORTAL_CSS_REL"
local portal_built_css_path="$portal_repo_path/$PORTAL_BUILT_CSS_REL"
local portal_meta_path="$portal_repo_path/$PORTAL_META_REL"
local portal_styleguide_docs_path="$portal_repo_path/$PORTAL_STYLEGUIDE_DOCS_REL"
local portal_build_script_path="$portal_repo_path/$PORTAL_BUILD_SCRIPT_REL"
local tmp_portal_css
if [[ ! -d "$portal_repo_path/.git" ]]; then
echo "Portal repo is not a git repository: $portal_repo_path" >&2
exit 1
fi
mkdir -p "$(dirname "$portal_css_path")"
mkdir -p "$portal_styleguide_docs_path"
tmp_portal_css="$(mktemp)"
build_portal_css "$portal_key" "$TMP_UPSTREAM_CSS" "$tmp_portal_css"
cp "$tmp_portal_css" "$portal_css_path"
rm -f "$tmp_portal_css"
rsync -a --delete \
--exclude ".git/" \
--exclude ".codex/" \
--exclude ".DS_Store" \
--exclude "AGENTS.md" \
"$STYLEGUIDE_REPO_ROOT/" \
"$portal_styleguide_docs_path/"
STYLEGUIDE_VERSION="$(tr -d '[:space:]' < "$VERSION_FILE")"
STYLEGUIDE_COMMIT="$(git -C "$STYLEGUIDE_REPO_ROOT" rev-parse --short HEAD)"
SYNCED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
cat > "$portal_meta_path" <<META
{
"styleguideVersion": "$STYLEGUIDE_VERSION",
"styleguideCommit": "$STYLEGUIDE_COMMIT",
"syncedAtUtc": "$SYNCED_AT",
"sourceRepo": "$STYLEGUIDE_REPO_ROOT",
"mirroredDocsPath": "$PORTAL_STYLEGUIDE_DOCS_REL"
}
META
case "$portal_key" in
vsf)
if [[ ! -f "$portal_build_script_path" ]]; then
echo "Portal build script not found: $portal_build_script_path" >&2
exit 1
fi
bash "$portal_build_script_path"
;;
naurua)
cp "$portal_css_path" "$portal_built_css_path"
;;
esac
if [[ "$COMMIT_IN_PORTAL" == "true" ]]; then
git -C "$portal_repo_path" add -A \
"$PORTAL_CSS_REL" \
"$PORTAL_BUILT_CSS_REL" \
"$PORTAL_META_REL" \
"$PORTAL_STYLEGUIDE_DOCS_REL"
if ! git -C "$portal_repo_path" diff --cached --quiet; then
git -C "$portal_repo_path" commit -m "Sync styleguide $STYLEGUIDE_VERSION"
git -C "$portal_repo_path" push
echo "$portal_key portal synced and pushed."
else
echo "No changes to commit in $portal_key portal repo."
fi
else
echo "$portal_key portal files updated locally (no commit requested)."
fi
echo "Synced $SOURCE_CSS -> $portal_css_path"
echo "Built portal stylesheet: $portal_built_css_path"
echo "Metadata written: $portal_meta_path"
echo "Mirrored styleguide docs: $portal_styleguide_docs_path"
}
assert_no_unimported_styles() {
local source_file="$1"
local source_root="$2"
local styles_dir="$source_root/styles"
local missing_imports=()
if [[ ! -d "$styles_dir" ]]; then
return 0
fi
while IFS= read -r css_file; do
local rel_path="./styles/$(basename "$css_file")"
if ! grep -Eq "^[[:space:]]*@import[[:space:]]+\"$rel_path\"[[:space:]]*;" "$source_file"; then
missing_imports+=("$rel_path")
fi
done < <(find "$styles_dir" -maxdepth 1 -type f -name '*.css' | sort)
if [[ ${#missing_imports[@]} -gt 0 ]]; then
echo "Unimported CSS module files detected in styles/:" >&2
for file in "${missing_imports[@]}"; do
echo " - $file" >&2
done
echo "Add missing @import lines in $source_file before syncing." >&2
exit 1
fi
}
TMP_UPSTREAM_CSS="$(mktemp)"
trap 'rm -f "$TMP_UPSTREAM_CSS"' EXIT
assert_no_unimported_styles "$SOURCE_CSS" "$STYLEGUIDE_REPO_ROOT"
flatten_css "$SOURCE_CSS" "$STYLEGUIDE_REPO_ROOT" "$TMP_UPSTREAM_CSS"
sync_portal "vsf" "/Users/mathias/Documents/Dokumente Chouchou/Codebases/WebApp_Aktienberater"
sync_portal "naurua" "/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua"
@@ -0,0 +1,246 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Semantic Tokens Components</title>
<link rel="stylesheet" href="./styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Semantic Tokens Components</h1>
<section id="semantic-cards">
<h2 class="sg-sub-heading sg-section-h2">Cards</h2>
<h3 class="sg-sub-heading sg-section-h3">Cards</h3>
<table class="sg-foundation-table sg-table-label">
<thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead>
<tbody>
<tr><td>surface-card</td><td>color-light-grey</td><td>Grundfläche der Card.</td></tr>
<tr><td>surface-card-body</td><td>color-light-grey</td><td>Fläche im Body-Segment der Card.</td></tr>
<tr><td>surface-card-segment-neutral</td><td>color-light-grey</td><td>Neutrale Segmentfläche für gezielte hellgraue Card-Segmente (z. B. <code>sg-card-segment--gray</code>).</td></tr>
<tr><td>surface-card-header-primary</td><td>color-darkblue</td><td>Primärer Header-Hintergrund.</td></tr>
<tr><td>surface-card-header-alternative</td><td>color-darkgreen</td><td>Alternativer Header-Hintergrund.</td></tr>
<tr><td>surface-card-header-muted</td><td>color-darkbrown</td><td>Zweite alternative Header-Fläche.</td></tr>
<tr><td>divider-card-segment</td><td>color-white</td><td>Trenner zwischen Segmenten.</td></tr>
<tr><td>text-card-header</td><td>color-font-light</td><td>Header-Textfarbe.</td></tr>
<tr><td>text-card-body</td><td>color-font-dark</td><td>Standard-Textfarbe im Body.</td></tr>
<tr><td>layout-card-body-content-justify</td><td>flex-start</td><td>Ausrichtung des Inhalts im Body-Segment der Card.</td></tr>
<tr><td>layout-card-segment-content-gap</td><td>spacing-small</td><td>Vertikaler Abstand zwischen Inhaltselementen innerhalb eines Card-Segments.</td></tr>
<tr><td>layout-card-segment-padding-top</td><td>card-segment-padding-vertical</td><td>Oberer Innenabstand von Card-Segmenten.</td></tr>
<tr><td>layout-card-segment-padding-right</td><td>card-segment-padding-horizontal</td><td>Rechter Innenabstand von Card-Segmenten.</td></tr>
<tr><td>layout-card-segment-padding-bottom</td><td>card-segment-padding-vertical</td><td>Unterer Innenabstand von Card-Segmenten.</td></tr>
<tr><td>layout-card-segment-padding-left</td><td>card-segment-padding-horizontal</td><td>Linker Innenabstand von Card-Segmenten.</td></tr>
<tr><td>layout-card-body-padding-top</td><td>layout-card-segment-padding-top</td><td>Oberer Innenabstand des Card-Body-Segments.</td></tr>
<tr><td>layout-card-body-padding-right</td><td>layout-card-segment-padding-right</td><td>Rechter Innenabstand des Card-Body-Segments.</td></tr>
<tr><td>layout-card-body-padding-bottom</td><td>layout-card-segment-padding-bottom</td><td>Unterer Innenabstand des Card-Body-Segments.</td></tr>
<tr><td>layout-card-body-padding-left</td><td>layout-card-segment-padding-left</td><td>Linker Innenabstand des Card-Body-Segments.</td></tr>
<tr><td>layout-card-body-text-margin</td><td>0</td><td>Textabstand im Card-Body; verhindert zusätzliche Default-Margins.</td></tr>
</tbody>
</table>
<p class="sg-table-label">Verhaltensregel: Für Cards mit ausklappenden Overlays (z. B. Help-Panel, Sandwich-Menü, Pulldown) ist die Komponenten-Variante <code>sg-card--overlay-host</code> zu verwenden, damit Overlays nicht abgeschnitten werden.</p>
<h3 class="sg-sub-heading sg-section-h3">Group Card</h3>
<table class="sg-foundation-table sg-table-label">
<thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead>
<tbody>
<tr><td>surface-card-group</td><td>color-background-purple-light</td><td>Fläche der eigentlichen Group Card (heller Container, der mehrere Cards gruppiert).</td></tr>
</tbody>
</table>
<h3 class="sg-sub-heading sg-section-h3">Pattern Card Breiten</h3>
<table class="sg-foundation-table sg-table-label">
<thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead>
<tbody>
<tr><td>layout-object-group-card-min-width</td><td>dimension-object-group-card-min-width</td><td>Mindestbreite der Karteninstanzen im Pattern Object Group Card.</td></tr>
<tr><td>layout-object-group-card-max-width</td><td>dimension-object-group-card-max-width</td><td>Maximalbreite der Karteninstanzen im Pattern Object Group Card.</td></tr>
<tr><td>layout-object-group-card-height</td><td>dimension-object-group-card-height</td><td>Fixe Desktop-Höhe der Karteninstanzen im Pattern Object Group Card.</td></tr>
<tr><td>layout-content-card-margin-top-desktop</td><td>dimension-content-card-margin-top-desktop</td><td>Oberer Außenabstand der Content Card auf Desktop.</td></tr>
<tr><td>layout-content-card-margin-top-mobile</td><td>dimension-content-card-margin-top-mobile</td><td>Oberer Außenabstand der Content Card auf Mobile.</td></tr>
<tr><td>layout-notifications-text-segment-fixed-height</td><td>150px</td><td>Fixe Desktop-Höhe des ersten Text-Segments im Pattern Notifications; auf Mobile wird die Höhe wieder auf auto gesetzt.</td></tr>
</tbody>
</table>
<h3 class="sg-sub-heading sg-section-h3">Text Group Box</h3>
<table class="sg-foundation-table sg-table-label">
<thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead>
<tbody>
<tr><td>surface-card-transparent</td><td>color-transparent</td><td>Transparente Fläche der Text-Group-Box ohne Card-Segmente.</td></tr>
<tr><td>text-card-transparent</td><td>color-font-light</td><td>Textfarbe der Text-Group-Box.</td></tr>
</tbody>
</table>
<h3 class="sg-sub-heading sg-section-h3">VSF Drawer Object Card</h3>
<table class="sg-foundation-table sg-table-label">
<thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead>
<tbody>
<tr><td>surface-vsf-drawer-object-card-header</td><td>color-darkblue</td><td>Header-Fläche der VSF-spezifischen Object Card im Drawer.</td></tr>
<tr><td>surface-vsf-drawer-object-card-body</td><td>color-darkblue</td><td>Body-Fläche der VSF-spezifischen Object Card im Drawer.</td></tr>
<tr><td>text-vsf-drawer-object-card-heading</td><td>color-font-light</td><td>Textfarbe der H2-Überschrift im Header der VSF Drawer Object Card.</td></tr>
<tr><td>text-vsf-drawer-object-card-body</td><td>color-font-light</td><td>Textfarbe der Detailinhalte im Body der VSF Drawer Object Card.</td></tr>
<tr><td>layout-vsf-drawer-object-card-column-gap</td><td>spacing-large</td><td>Spaltenabstand des zweispaltigen Textlayouts in der VSF Drawer Object Card.</td></tr>
</tbody>
</table>
</section>
<section id="semantic-interactive">
<h2 class="sg-sub-heading sg-section-h2">Interactive Elements</h2>
<h3 class="sg-sub-heading sg-section-h3">Button</h3>
<table class="sg-foundation-table sg-table-label">
<thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead>
<tbody>
<tr><td>surface-button-active</td><td>color-medium-grey</td><td>Fläche aktiver Standard-Buttons.</td></tr>
<tr><td>surface-button-inactive</td><td>color-light-grey</td><td>Fläche inaktiver Standard-Buttons.</td></tr>
<tr><td>surface-button-process</td><td>color-font-hyperlink</td><td>Fläche von Prozess-/CTA-Buttons.</td></tr>
<tr><td>surface-button-process-inactive</td><td>color-process-inactive</td><td>Fläche inaktiver Prozess-/CTA-Buttons.</td></tr>
<tr><td>text-button-process</td><td>color-font-light</td><td>Textfarbe auf Prozess-/CTA-Buttons.</td></tr>
</tbody>
</table>
<h3 class="sg-sub-heading sg-section-h3">Tabs</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-tab-selected</td><td>color-dark-grey</td><td>Fläche des ausgewählten Tabs.</td></tr>
<tr><td>surface-tab-unselected</td><td>color-white</td><td>Fläche nicht ausgewählter Tabs.</td></tr>
<tr><td>surface-tab-compact-background</td><td>surface-form-preview</td><td>Hintergrundfläche kompakter Tab-Navigationen.</td></tr>
<tr><td>surface-tab-compact-unselected</td><td>color-medium-grey</td><td>Fläche nicht aktiver Tabs in der kompakten Variante.</td></tr>
<tr><td>text-tab-selected</td><td>color-font-light</td><td>Textfarbe des ausgewählten Tabs.</td></tr>
<tr><td>text-tab-unselected</td><td>color-dark-grey</td><td>Textfarbe nicht ausgewählter Tabs.</td></tr>
<tr><td>layout-tab-navigation-large-padding-inline</td><td>interaction-padding-horizontal</td><td>Horizontaler Innenabstand für große Tab-Navigationen (auf kleinen Viewports reduziert).</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Form Controls</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-control-default</td><td>color-white</td><td>Standardfläche für Input-/Auswahl-Controls.</td></tr>
<tr><td>surface-control-active</td><td>color-white</td><td>Fläche aktiver Controls.</td></tr>
<tr><td>surface-control-inactive</td><td>color-white</td><td>Fläche inaktiver, aber wählbarer Controls.</td></tr>
<tr><td>surface-control-disabled</td><td>color-white</td><td>Fläche deaktivierter Controls.</td></tr>
<tr><td>text-control-default</td><td>color-font-dark</td><td>Standard-Textfarbe in Controls.</td></tr>
<tr><td>text-control-disabled</td><td>color-dark-grey</td><td>Textfarbe deaktivierter Controls.</td></tr>
<tr><td>surface-input-clear</td><td>color-medium-grey</td><td>Fläche des Clear-Buttons in Inputs.</td></tr>
<tr><td>layout-input-label-width</td><td>dimension-input-label-width</td><td>Desktop-Breite der Label-Spalte für gruppierte Formularzeilen mit Pulldowns, Slidern, Radio-Feldern, Checkbox-Feldern sowie ein- und mehrzeiligen Eingabefeldern.</td></tr>
<tr><td>layout-input-field-desktop-width</td><td>dimension-input-field-desktop-width</td><td>Fixe Desktop-Breite für ein- und mehrzeilige Eingabefelder.</td></tr>
<tr><td>layout-input-field-max-width</td><td>dimension-input-field-max-width</td><td>Maximale Breite für ein- und mehrzeilige Eingabefelder (ohne Suchfeld).</td></tr>
<tr><td>layout-search-field-width</td><td>dimension-search-field-width</td><td>Fixe Breite des Suchfeld-Inputs.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Pulldown and Menu</h3>
<p class="sg-table-label">Changelog (2026-05-25): Overlay-Layer vereinheitlicht; Pulldowns liegen über Scorebars (Fix Relativmodus).</p>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-pulldown-panel</td><td>color-light-grey</td><td>Fläche geöffneter Pulldown-Panels.</td></tr>
<tr><td>surface-activatable-remove</td><td>surface-control-default</td><td>Fläche des Schließen-Kontrollfelds für aktive Filterzeilen innerhalb geöffneter Pulldown-Panel-Formbereiche.</td></tr>
<tr><td>icon-pulldown-chevron</td><td>currentColor-basierte Verlaufsgrafik</td><td>Chevron-Icon für Pulldown-Trigger und Select-Felder; folgt der aktuellen Textfarbe.</td></tr>
<tr><td>layout-pulldown-panel-padding-inline</td><td>compact-interaction-padding-horizontal</td><td>Horizontaler Innenabstand des geöffneten Pulldown-Panels.</td></tr>
<tr><td>layout-pulldown-option-padding-inline</td><td>compact-interaction-padding-horizontal</td><td>Horizontaler Innenabstand der einzelnen Pulldown-Optionen.</td></tr>
<tr><td>layout-pulldown-padding-inline</td><td>compact-interaction-padding-horizontal</td><td>Innenabstand links für Pulldown-Trigger und Select-Felder.</td></tr>
<tr><td>layout-pulldown-chevron-offset</td><td>compact-interaction-padding-horizontal</td><td>Rechter Innenabstand des Chevron-Icons im Pulldown.</td></tr>
<tr><td>layout-pulldown-chevron-reserved-space</td><td>spacing-large</td><td>Reservierte Breite zwischen Pulldown-Text und rechtem Chevron-Bereich.</td></tr>
<tr><td>layout-pulldown-panel-form-mobile-width</td><td>90%</td><td>Zielbreite für Pulldown-Panels mit Formularinhalt auf Mobile; wird durch die Viewport-Kappe begrenzt.</td></tr>
<tr><td>layer-pulldown-panel</td><td>dimension-layer-pulldown-panel</td><td>Layer-Stufe geöffneter Pulldown-Ausklappfelder über übrigen Seiteninhalten.</td></tr>
<tr><td>layout-multiselect-pulldown-panel-desktop-width</td><td>dimension-multiselect-pulldown-panel-desktop-width</td><td>Semantische Obergrenze für die Panelbreite geöffneter Pulldowns; das Panel darf über Triggerbreite wachsen, bleibt aber durch diese Breite und die Viewport-Kappe begrenzt.</td></tr>
<tr><td>surface-menu-panel-portal</td><td>color-light-grey</td><td>Fläche des Sandwich-Menü-Panels für große und kleine Variante.</td></tr>
<tr><td>text-menu-link-portal</td><td>color-font-dark</td><td>Linktextfarbe im Sandwich-Menü für große und kleine Variante.</td></tr>
<tr><td>text-activatable-remove</td><td>text-control-default</td><td>Zeichenfarbe des Schließen-Kontrollfelds für aktive Filterzeilen innerhalb geöffneter Pulldown-Panel-Formbereiche.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Toggle / Checkbox / Radio / Help</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-toggle-track</td><td>color-medium-grey</td><td>Toggle-Track-Fläche.</td></tr>
<tr><td>surface-toggle-handle</td><td>color-darkblue</td><td>Toggle-Handle-Fläche.</td></tr>
<tr><td>text-toggle-label-active</td><td>color-font-light</td><td>Textfarbe des aktiven Toggle-Labels auf der hervorgehobenen Handle-Seite.</td></tr>
<tr><td>layout-mode-toggle-local-height</td><td>compact-interaction-height</td><td>Höhe des Modus Schieber lokal; entspricht dem kompakten Interaktionsmaß.</td></tr>
<tr><td>layout-mode-toggle-local-width-factor</td><td>3</td><td>Breitenfaktor des lokalen Modus-Schiebers relativ zur lokalen Höhe.</td></tr>
<tr><td>layout-mode-toggle-width</td><td>calc(interaction-height * 3.5)</td><td>Standardbreite des Mode-Toggles in regulären Varianten.</td></tr>
<tr><td>surface-checkbox-default</td><td>color-white</td><td>Standardfläche der Checkbox.</td></tr>
<tr><td>surface-checkbox-on-context</td><td>color-white</td><td>Checkbox-Fläche auf Kontextflächen.</td></tr>
<tr><td>surface-radio-default</td><td>color-white</td><td>Standardfläche des Radio-Buttons.</td></tr>
<tr><td>icon-radio-mark</td><td>color-font-dark</td><td>Markierungsfarbe im Radio-Button.</td></tr>
<tr><td>surface-help-icon</td><td>color-medium-grey</td><td>Fläche des Help-Icons.</td></tr>
<tr><td>surface-help-panel</td><td>color-light-grey</td><td>Fläche des Help-Panels.</td></tr>
<tr><td>text-help-icon</td><td>color-font-dark</td><td>Textfarbe im Help-Icon.</td></tr>
<tr><td>text-help-panel</td><td>color-font-dark</td><td>Textfarbe im Help-Panel.</td></tr>
<tr><td>layout-help-panel-width</td><td>calc(interaction-height * 8)</td><td>Standardbreite des Help-Panels.</td></tr>
<tr><td>icon-sandwich-line-portal</td><td>color-font-dark</td><td>Linienfarbe des Sandwich-Icons für große und kleine Variante.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Slider</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-slider-track</td><td>color-medium-grey</td><td>Grundfläche des Slider-Strichs.</td></tr>
<tr><td>surface-slider-progress</td><td>color-dark-grey</td><td>Ausgewählter Bereich links vom Slider-Knopf.</td></tr>
<tr><td>surface-slider-thumb</td><td>color-dark-grey</td><td>Gefüllte Fläche des Slider-Knopfs (ohne Linie).</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Preview Area</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-form-preview</td><td>color-light-grey</td><td>Fläche von Formular-/Control-Preview-Bereichen.</td></tr>
<tr><td>text-hyperlink</td><td>color-font-hyperlink</td><td>Linkfarbe in interaktiven Texten.</td></tr>
</tbody></table>
</section>
<section id="semantic-charts">
<h2 class="sg-sub-heading sg-section-h2">Charts</h2>
<h3 class="sg-sub-heading sg-section-h3">Score Bar</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-score-bar-track</td><td>color-medium-grey</td><td>Hintergrund der Score-Bar.</td></tr>
<tr><td>chart-value-positive</td><td>color-signal-green</td><td>Füllfarbe positiver Werte.</td></tr>
<tr><td>chart-value-neutral</td><td>color-signal-yellow</td><td>Füllfarbe neutraler Werte.</td></tr>
<tr><td>chart-value-negative</td><td>color-signal-red</td><td>Füllfarbe negativer Werte.</td></tr>
<tr><td>chart-median-line</td><td>color-font-dark</td><td>Farbe der Median-Markierung.</td></tr>
<tr><td>text-score-state-positive</td><td>chart-value-positive</td><td>Textfarbe positiver Zustandslabels am Gesamtscore-Balken.</td></tr>
<tr><td>text-score-state-warning</td><td>chart-value-neutral</td><td>Textfarbe der warnenden bzw. gelben Zustandslabels am Gesamtscore-Balken.</td></tr>
<tr><td>text-score-state-neutral</td><td>text-score-state-warning</td><td>Alias für die neutrale Zustandsfarbe am Gesamtscore-Balken; bleibt mit dem bestehenden Component-Contract kompatibel.</td></tr>
<tr><td>text-score-state-negative</td><td>chart-value-negative</td><td>Textfarbe negativer Zustandslabels am Gesamtscore-Balken.</td></tr>
<tr><td>layout-score-bar-item-columns</td><td>max-content und 1fr</td><td>Standardspalten der Score-Bar-Zeile: dynamische Labelspalte (längstes Label) plus Balkenspur.</td></tr>
<tr><td>layout-score-bar-item-gap</td><td>spacing-large</td><td>Abstand zwischen Labelspalte und Balkenspur in der Score-Bar-Zeile.</td></tr>
<tr><td>layout-score-bar-item-single-score-columns</td><td>max-content, 1fr und max-content</td><td>Spalten der Score-Bar-Zeile in der Single-Score-Variante: Label, Balken, Zustandslabel.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Bar / Line Chart</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-chart-area</td><td>color-light-grey</td><td>Grundfläche des Charts.</td></tr>
<tr><td>chart-value-primary</td><td>color-darkblue</td><td>Primäre Datenreihe.</td></tr>
<tr><td>chart-value-reference</td><td>color-medium-grey</td><td>Referenz-/Vergleichsreihe.</td></tr>
<tr><td>chart-grid-line</td><td>color-medium-grey</td><td>Gridline-Farbe.</td></tr>
<tr><td>chart-axis-line</td><td>color-font-dark</td><td>Achsenfarbe.</td></tr>
<tr><td>chart-marker-line</td><td>color-font-dark</td><td>Marker-/Hilfslinienfarbe.</td></tr>
<tr><td>text-chart-default</td><td>color-font-dark</td><td>Textfarbe in Achsen/Labels.</td></tr>
</tbody></table>
</section>
<section id="semantic-data-display">
<h2 class="sg-sub-heading sg-section-h2">Data Display</h2>
<h3 class="sg-sub-heading sg-section-h3">Data Table Small</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-data-table</td><td>color-light-grey</td><td>Gesamtfläche der Tabelle.</td></tr>
<tr><td>surface-data-table-header</td><td>color-light-grey</td><td>Fläche der Tabellenkopfzeile.</td></tr>
<tr><td>surface-data-table-cell</td><td>color-light-grey</td><td>Fläche von Tabellenzellen.</td></tr>
<tr><td>surface-data-table-help-icon</td><td>color-medium-grey</td><td>Fläche des Help-Icons im Tabellenkontext.</td></tr>
<tr><td>text-data-table-default</td><td>color-font-dark</td><td>Standard-Textfarbe in Tabellenzellen.</td></tr>
<tr><td>text-data-table-warning</td><td>color-signal-yellow</td><td>Warnwertfarbe in Tabellenzellen.</td></tr>
<tr><td>text-data-table-help-icon</td><td>color-font-dark</td><td>Textfarbe im Tabellen-Help-Icon.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Data Columns</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>surface-data-table</td><td>color-light-grey</td><td>Gesamtfläche der Spaltenliste.</td></tr>
<tr><td>surface-data-table-cell</td><td>color-light-grey</td><td>Fläche von Spaltenzellen.</td></tr>
<tr><td>text-data-table-default</td><td>color-font-dark</td><td>Standard-Textfarbe in Spaltenzellen.</td></tr>
<tr><td>text-data-table-warning</td><td>color-signal-yellow</td><td>Warnwertfarbe in Spaltenzellen.</td></tr>
</tbody></table>
</section>
<section id="semantic-typography">
<h2 class="sg-sub-heading sg-section-h2">Typography</h2>
<h3 class="sg-sub-heading sg-section-h3">Typography Preview</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck in der Komponente</th></tr></thead><tbody>
<tr><td>text-typography-preview</td><td>color-font-light</td><td>Textfarbe in der Typografie-Preview.</td></tr>
<tr><td>layout-preview-align-items</td><td>flex-start</td><td>Vertikale Ausrichtung der Elemente innerhalb der Typografie-Preview.</td></tr>
</tbody></table>
</section>
</body>
</html>
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Semantic Tokens Layouts</title>
<link rel="stylesheet" href="./styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Semantic Tokens Layouts</h1>
<section id="semantic-layouts">
<h2 class="sg-sub-heading sg-section-h2">Layouts</h2>
<h3 class="sg-sub-heading sg-section-h3">Generische Layouts</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck im Layout</th></tr></thead><tbody>
<tr><td>layout-options-row-margin-top</td><td>spacing-small</td><td>Abstand oberhalb der Options Row.</td></tr>
<tr><td>layout-options-row-main-gap</td><td>spacing-large</td><td>Horizontaler Abstand zwischen linker und rechter Aktionsgruppe.</td></tr>
<tr><td>layout-options-row-group-gap</td><td>spacing-small</td><td>Abstand zwischen Controls innerhalb der linken und rechten Gruppe.</td></tr>
<tr><td>layout-options-row-mode-toggle-width</td><td>dimension-options-row-mode-toggle-width</td><td>Breite des Mode-Toggles innerhalb der Options Row.</td></tr>
<tr><td>layout-options-row-help-panel-width</td><td>dimension-options-row-help-panel-width</td><td>Breite des Help-Panels innerhalb der Options Row.</td></tr>
<tr><td>layout-object-card-min-width</td><td>dimension-object-card-min-width</td><td>Mindestbreite der Object Card im horizontalen Desktop-Layout.</td></tr>
<tr><td>layout-object-card-max-width</td><td>dimension-object-card-max-width</td><td>Maximalbreite der Object Card im horizontalen Desktop-Layout.</td></tr>
<tr><td>layout-object-card-height</td><td>dimension-object-card-height</td><td>Fixe Höhe der Object Card im Desktop-Layout.</td></tr>
<tr><td>layout-object-card-mobile-width</td><td>dimension-object-card-mobile-width</td><td>Volle verfügbare Breite der Object Card auf Mobile.</td></tr>
<tr><td>layout-object-card-mobile-height</td><td>dimension-object-card-mobile-height</td><td>Inhaltsabhängige Höhe der Object Card auf Mobile.</td></tr>
<tr><td>layout-object-card-desktop-breakpoint</td><td>dimension-object-card-desktop-breakpoint</td><td>Breakpoint, ab dem die Desktop-Maße der Object Card aktiv sind.</td></tr>
<tr><td>layout-object-card-content-grow</td><td>dimension-object-card-content-grow</td><td>Flex-Grow-Wert für das vorletzte Segment der Object Card.</td></tr>
<tr><td>layout-object-card-shared-width</td><td>auto</td><td>Geteilte Laufzeitbreite für gekoppelte Object-Card-Instanzen.</td></tr>
<tr><td>layout-content-cards-group-gap</td><td>spacing-small</td><td>Abstand zwischen Inhaltsblock-Karten innerhalb der Gruppe.</td></tr>
<tr><td>layout-card-list-drawer-width</td><td>dimension-card-list-drawer-width</td><td>Breite des ausziehbaren Card-Listenbereichs relativ zum Container.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Valuestockfinder Layouts</h3>
<table class="sg-foundation-table sg-table-label"><thead><tr><th>Semantischer Token</th><th>Verwendeter Foundation-Token</th><th>Beschreibung Verwendungszweck im Layout</th></tr></thead><tbody>
<tr><td>layout-company-card-analysis-gap-after-moat</td><td>spacing-large</td><td>Vertikaler Abstand zwischen Moat-Zeile und Analyse-Zusammenfassung in der Company Card.</td></tr>
</tbody></table>
</section>
</body>
</html>
@@ -0,0 +1,131 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styleguide Semantic Tokens Patterns</title>
<link rel="stylesheet" href="./styleguide.css">
</head>
<body>
<h1 class="sg-main-heading">Semantic Tokens Patterns</h1>
<section id="semantic-patterns">
<h2 class="sg-sub-heading sg-section-h2">Patterns</h2>
<h3 class="sg-sub-heading sg-section-h3">Portal Header</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-portal-header</td><td>color-darkblue</td><td>Header-Grundfläche.</td></tr>
<tr><td>surface-portal-header-tab</td><td>color-midblue</td><td>Fläche nicht aktiver Header-Tabs.</td></tr>
<tr><td>surface-portal-header-tab-active</td><td>color-light-grey</td><td>Fläche aktiver Header-Tabs.</td></tr>
<tr><td>text-portal-header-brand</td><td>color-font-light</td><td>Textfarbe der Brand.</td></tr>
<tr><td>font-size-portal-header-brand</td><td>calc(var(--font-size-brand) * 1.1)</td><td>Schriftgröße der Brand im Portal Header (10% größer als Standard-Brand).</td></tr>
<tr><td>text-portal-header-tab</td><td>color-font-light</td><td>Textfarbe nicht aktiver Header-Tabs.</td></tr>
<tr><td>text-portal-header-tab-active</td><td>color-font-dark</td><td>Textfarbe aktiver Header-Tabs.</td></tr>
<tr><td>layout-page-content-inset-inline</td><td>card-segment-padding-horizontal</td><td>Gemeinsamer horizontaler Content-Inset für Portal Header, Options Row und Card-Listen-Drawer-Content (Single Source of Spacing Truth auf Pattern-Ebene).</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Options Row</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-options-row</td><td>color-light-grey</td><td>Grundfläche der Options Row.</td></tr>
<tr><td>surface-options-row-control</td><td>surface-control-default</td><td>Alias auf Standard-Control-Fläche innerhalb der Options Row.</td></tr>
<tr><td>surface-options-row-control-selected</td><td>surface-control-active</td><td>Alias auf aktive Control-Fläche innerhalb der Options Row.</td></tr>
<tr><td>surface-options-row-input-clear</td><td>surface-input-clear</td><td>Alias auf Clear-Button-Fläche im Input der Options Row.</td></tr>
<tr><td>surface-options-row-toggle-track</td><td>surface-toggle-track</td><td>Alias auf Toggle-Track-Fläche innerhalb der Options Row.</td></tr>
<tr><td>surface-options-row-toggle-handle</td><td>surface-toggle-handle</td><td>Alias auf Toggle-Handle-Fläche innerhalb der Options Row.</td></tr>
<tr><td>surface-options-row-help-icon</td><td>surface-help-icon</td><td>Alias auf Help-Icon-Fläche innerhalb der Options Row.</td></tr>
<tr><td>surface-options-row-help-panel</td><td>surface-help-panel</td><td>Alias auf Help-Panel-Fläche innerhalb der Options Row.</td></tr>
<tr><td>divider-options-row-mobile</td><td>color-white</td><td>Trennerfarbe zwischen Segmenten im mobilen Layout der Options Row.</td></tr>
<tr><td>text-options-row-default</td><td>text-control-default</td><td>Alias auf Standard-Textfarbe für Controls in der Options Row.</td></tr>
<tr><td>text-options-row-placeholder</td><td>text-control-disabled</td><td>Alias auf Placeholder-/Disabled-Textfarbe in der Options Row.</td></tr>
<tr><td>text-options-row-help-icon</td><td>color-font-light</td><td>Textfarbe im Help-Icon der Options Row.</td></tr>
<tr><td>text-options-row-help-panel</td><td>text-help-panel</td><td>Alias auf Help-Panel-Textfarbe der Options Row.</td></tr>
<tr><td>text-options-row-description</td><td>color-font-light</td><td>Textfarbe der beschreibenden Hinweistexte oberhalb der Options Row.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Object Card</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-object-card-lower-segment</td><td>color-white</td><td>Hintergrundfläche der unteren zwei Segmente der Object Card.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Object Group Card</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>layout-object-group-card-min-width</td><td>dimension-object-group-card-min-width</td><td>Mindestbreite der einzelnen Karten im Pattern Object Group Card.</td></tr>
<tr><td>layout-object-group-card-max-width</td><td>dimension-object-group-card-max-width</td><td>Maximalbreite der einzelnen Karten im Pattern Object Group Card.</td></tr>
<tr><td>layout-object-group-card-height</td><td>dimension-object-group-card-height</td><td>Fixe Desktop-Höhe der einzelnen Karten im Pattern Object Group Card.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Notifications</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>layout-notifications-card-flex-basis</td><td>dimension-notifications-card-min-width</td><td>Flex-Basis der Notification Card im Notifications-Pattern; entspricht der gemeinsamen Foundation-Mindestbreite und gilt auch in der dokumentierten Variante innerhalb von <code>.sg-group-card</code>.</td></tr>
<tr><td>layout-notifications-card-min-width</td><td>dimension-notifications-card-min-width</td><td>Mindestbreite der Notification Card im Notifications-Pattern; 50px größer als die Object Card und verhindert zu frühes Schrumpfen bei Viewport-Änderungen.</td></tr>
<tr><td>layout-notifications-card-max-width</td><td>layout-object-card-max-width</td><td>Maximalbreite der Notification Card im Notifications-Pattern; begrenzt Wachstum konsistent zur Object-Card-Breite.</td></tr>
<tr><td>layout-notifications-text-segment-fixed-height</td><td>dimension-notifications-text-segment-fixed-height</td><td>Fixe Desktop-Höhe des ersten Text-Segments im Notifications-Pattern; auf Mobile wird die Höhe auf auto gesetzt.</td></tr>
<tr><td>layout-notifications-text-segment-fixed-height-small</td><td>dimension-notifications-text-segment-fixed-height-small</td><td>Fixe Desktop-Höhe des ersten Text-Segments in der Variante <code>Pattern: Notifications small</code>; auf Mobile wird die Höhe auf auto gesetzt.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Company Card</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>text-company-card-data-negative</td><td>chart-value-negative</td><td>Textfarbe für negative Kennzahlenwerte im Data-Columns-Segment der Company Card.</td></tr>
<tr><td>text-company-card-moat-neutral</td><td>chart-value-neutral</td><td>Textfarbe für die neutrale Moat-Ausprägung im Analyse-Segment der Company Card.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Navigation Card</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-navigation-card</td><td>color-white</td><td>Grundfläche der Navigation Card.</td></tr>
<tr><td>surface-navigation-card-body</td><td>color-white</td><td>Fläche im Body-Segment der Navigation Card.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Card Gruppe mit Tastennavigation</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-content-block-card-title</td><td>color-light-grey</td><td>Hintergrundfläche des oberen Titel-Segments der Inhaltsblock-Karte.</td></tr>
<tr><td>surface-content-block-card-content</td><td>color-white</td><td>Hintergrundfläche des unteren Inhalts-Segments der Inhaltsblock-Karte.</td></tr>
<tr><td>text-content-block-card-title</td><td>color-font-dark</td><td>Textfarbe im Titel-Segment der Inhaltsblock-Karte.</td></tr>
<tr><td>text-content-block-card-content</td><td>color-font-dark</td><td>Textfarbe im Inhalts-Segment der Inhaltsblock-Karte.</td></tr>
<tr><td>surface-card-list-drawer</td><td>color-background-purple</td><td>Fläche des ausziehbaren Card-Listenbereichs.</td></tr>
<tr><td>text-card-list-drawer</td><td>color-font-dark</td><td>Textfarbe im ausziehbaren Card-Listenbereich.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Text Layouts</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-text-layout-preview</td><td>color-light-grey</td><td>Hintergrundfläche der Vorschau-Segmente im Pattern Text Layouts.</td></tr>
<tr><td>layout-text-layout-column-gap</td><td>spacing-large</td><td>Horizontaler Abstand zwischen Textspalten in zwei- und dreispaltigen Varianten.</td></tr>
<tr><td>layout-text-layout-two-column-columns</td><td>repeat(2, minmax(0, 1fr))</td><td>Spaltenraster für die zweispaltige Text-Variante.</td></tr>
<tr><td>layout-text-layout-three-column-columns</td><td>repeat(3, minmax(0, 1fr))</td><td>Spaltenraster für das Pattern Dreispaltig verteilt.</td></tr>
<tr><td>text-align-text-layout-column-left</td><td>left</td><td>Textausrichtung der linken Spalte im Pattern Dreispaltig verteilt.</td></tr>
<tr><td>text-align-text-layout-column-center</td><td>center</td><td>Textausrichtung der mittleren Spalte im Pattern Dreispaltig verteilt.</td></tr>
<tr><td>text-align-text-layout-column-right</td><td>right</td><td>Textausrichtung der rechten Spalte im Pattern Dreispaltig verteilt.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">VSF List Card</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>text-vsf-list-card-limit-note</td><td>color-signal-red</td><td>Hinweistextfarbe für die Meldung bei erreichter maximaler Listenanzahl.</td></tr>
<tr><td>layout-vsf-list-card-summary-offset-block-start</td><td>layout-company-card-analysis-gap-after-moat</td><td>Vertikaler Abstand vor dem Summary-Block in der VSF List Card.</td></tr>
<tr><td>layout-vsf-list-card-delete-confirmation-target-max-width</td><td>layout-object-group-card-max-width</td><td>Maximalbreite der Delete-Confirmation-Zielkarte innerhalb der VSF-List-Card-Demo.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Multiselektions-Pulldown</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>layout-multiselect-pulldown-label-column-width-fallback</td><td>max-content</td><td>Fallback-Breite der gemeinsamen Labelspalte im Panel, bis die längste Labelbreite per Pattern-Logik ermittelt wurde.</td></tr>
</tbody></table>
<h3 class="sg-sub-heading sg-section-h3">Overlay Card</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-delete-confirmation-overlay</td><td>color-light-grey</td><td>Hintergrundfläche des schwebenden Bestätigungsfensters.</td></tr>
<tr><td>surface-delete-confirmation-target-dim-overlay</td><td>color-black</td><td>Overlay-Farbe zur Ausgrauung des Zielobjekts hinter dem Bestätigungsfenster.</td></tr>
<tr><td>text-delete-confirmation-overlay</td><td>color-font-dark</td><td>Textfarbe im Bestätigungsfenster.</td></tr>
<tr><td>layout-delete-confirmation-target-max-width</td><td>35rem</td><td>Maximalbreite der abgedunkelten Ziel-Card in der Pattern-Demo.</td></tr>
<tr><td>layout-delete-confirmation-overlay-width-factor</td><td>0.8</td><td>Breitenfaktor des schwebenden Bestätigungsfensters relativ zur Breite des Zielobjekts (80%).</td></tr>
<tr><td>layout-delete-confirmation-overlay-offset-block-start</td><td>10%</td><td>Vertikaler Abstand des Overlays vom oberen Rand des Zielobjekts, relativ zur Objektgröße.</td></tr>
<tr><td>layout-delete-confirmation-overlay-max-height</td><td>calc(100vh - 10vh)</td><td>Maximalhöhe des Overlays relativ zum Viewport; danach scrollt der Inhalt innerhalb des Overlays.</td></tr>
<tr><td>layout-delete-confirmation-content-gap</td><td>spacing-small</td><td>Vertikaler Abstand zwischen den Inhaltsblöcken im Overlay.</td></tr>
<tr><td>layout-delete-confirmation-actions-gap</td><td>spacing-small</td><td>Horizontaler Abstand zwischen Abbrechen- und Löschen-Button.</td></tr>
<tr><td>layout-delete-confirmation-actions-offset-block-start</td><td>spacing-large</td><td>Zusätzlicher vertikaler Abstand zwischen Bestätigungs-Eingabefeld und Actions-Zeile.</td></tr>
<tr><td>layout-delete-confirmation-label-width</td><td>dimension-input-label-width</td><td>Label-Spaltenbreite der einzeiligen Bestätigungseingabe.</td></tr>
<tr><td>layout-delete-confirmation-target-dim-opacity</td><td>0.5</td><td>Deckkraft der grauen Überlagerung auf dem zu löschenden Objekt (50% ausgegraut).</td></tr>
<tr><td>layer-delete-confirmation-overlay</td><td>50</td><td>Z-Index-Layer des schwebenden Bestätigungsfensters.</td></tr>
</tbody></table>
</section>
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
@import "./styles/01-foundations.css";
@import "./styles/02-base.css";
@import "./styles/03-typography-helpers.css";
@import "./styles/10-components-interactive-elements.css";
@import "./styles/20-patterns-portal-header.css";
@import "./styles/21-patterns-options-row.css";
@import "./styles/22-patterns-object-card.css";
@import "./styles/23-patterns-object-group-card.css";
@import "./styles/24-patterns-navigation-card.css";
@import "./styles/25-patterns-form-sections.css";
@import "./styles/26-patterns-vsf-list-card.css";
@import "./styles/27-patterns-delete-confirmation.css";
@import "./styles/28-patterns-notifications.css";
@import "./styles/29-patterns-left-navigation.css";
@import "./styles/30-layouts-card-list-page.css";
@import "./styles/36-layouts-page-layout-app.css";
@import "./styles/37-layouts-page-layout-public.css";
@import "./styles/33-layouts-vsf-list-detailseite.css";
@import "./styles/34-layouts-vsf-listen-uebersicht-seite-v2.css";
@import "./styles/35-layouts-vsf-register-step-1.css";
@import "./styles/31-patterns-text-layouts.css";
@import "./styles/32-patterns-card-group-keyboard-nav.css";
@import "./styles/40-components-cards.css";
@import "./styles/41-components-charts.css";
@import "./styles/42-components-data-display.css";
@import "./styles/43-components-typography.css";
+320
View File
@@ -0,0 +1,320 @@
/* ========================================================= */
/* Foundations */
/* ========================================================= */
:root {
/* Colors */
--color-darkblue: #4661A9;
--color-midblue: #657FBA;
--color-darkgreen: #4D646E;
--color-darkbrown: #665F57;
--color-light-grey: #E2E5E9;
--color-medium-grey: #CDCFD4;
--color-dark-grey: #7B879D;
--color-black: #000000;
--color-white: #FFFFFF;
--color-process-inactive: #FFAE79;
--color-signal-green: #009101;
--color-signal-yellow: #9C7A00;
--color-signal-red: #9B3B2F;
--color-font-dark: #414959;
--color-font-light: #FFFFFF;
--color-font-hyperlink: #FF6900;
--color-background-purple: #373C4A;
--color-background-purple-light: #656C7D;
--color-transparent: transparent;
/* Semantic component tokens: Cards */
--surface-card: var(--color-light-grey);
--surface-card-body: var(--color-light-grey);
--surface-card-segment-neutral: var(--color-light-grey);
--surface-card-group: var(--color-background-purple-light);
--surface-card-transparent: var(--color-transparent);
--surface-card-header-primary: var(--color-darkblue);
--surface-card-header-alternative: var(--color-darkgreen);
--surface-card-header-muted: var(--color-darkbrown);
--divider-card-segment: var(--color-white);
--text-card-header: var(--color-font-light);
--text-card-body: var(--color-font-dark);
--text-card-transparent: var(--color-font-light);
--layout-card-body-content-justify: flex-start;
--layout-card-segment-content-gap: var(--spacing-small);
/* Semantic component tokens: Interactive elements */
--surface-form-preview: var(--color-light-grey);
--surface-control-default: var(--color-white);
--surface-control-active: var(--color-white);
--surface-control-inactive: var(--color-white);
--surface-control-disabled: var(--color-white);
--surface-button-active: var(--color-medium-grey);
--surface-button-process: var(--color-font-hyperlink);
--surface-button-process-inactive: var(--color-process-inactive);
--surface-button-inactive: var(--color-light-grey);
--surface-tab-selected: var(--color-dark-grey);
--surface-tab-unselected: var(--color-white);
--surface-tab-compact-background: var(--surface-form-preview);
--surface-tab-compact-unselected: var(--color-medium-grey);
--surface-input-clear: var(--color-medium-grey);
--surface-toggle-track: var(--color-medium-grey);
--surface-toggle-handle: var(--color-darkblue);
--surface-menu-panel-portal: var(--color-light-grey);
--surface-help-icon: var(--color-medium-grey);
--surface-help-panel: var(--color-light-grey);
--surface-pulldown-panel: var(--color-light-grey);
--surface-activatable-remove: var(--surface-control-default);
--surface-slider-track: var(--color-medium-grey);
--surface-slider-progress: var(--color-dark-grey);
--surface-slider-thumb: var(--color-dark-grey);
--surface-checkbox-default: var(--color-white);
--surface-checkbox-on-context: var(--color-white);
--surface-radio-default: var(--color-white);
--layout-pulldown-panel-padding-inline: var(--compact-interaction-padding-horizontal);
--layout-pulldown-option-padding-inline: var(--compact-interaction-padding-horizontal);
--layout-pulldown-padding-inline: var(--compact-interaction-padding-horizontal);
--layout-pulldown-chevron-offset: var(--compact-interaction-padding-horizontal);
--layout-tab-navigation-large-padding-inline: var(--interaction-padding-horizontal);
--layout-pulldown-chevron-reserved-space: var(--spacing-large);
--layout-pulldown-panel-form-mobile-width: 90%;
--layer-pulldown-panel: var(--dimension-layer-pulldown-panel);
--layout-multiselect-pulldown-panel-desktop-width: var(--dimension-multiselect-pulldown-panel-desktop-width);
--layout-multiselect-pulldown-label-column-width-fallback: max-content;
--layout-input-label-width: var(--dimension-input-label-width);
--layout-input-field-desktop-width: var(--dimension-input-field-desktop-width);
--layout-input-field-max-width: var(--dimension-input-field-max-width);
--layout-form-input-field-max-width: calc(var(--layout-input-field-max-width) + 100px);
--layout-search-field-width: var(--dimension-search-field-width);
--layout-mode-toggle-local-height: var(--compact-interaction-height);
--layout-mode-toggle-local-width-factor: 3;
--layout-mode-toggle-width: calc(var(--interaction-height) * 3.5);
--layout-help-panel-width: calc(var(--interaction-height) * 8);
--text-control-default: var(--color-font-dark);
--text-control-disabled: var(--color-dark-grey);
--text-button-process: var(--color-font-light);
--text-tab-selected: var(--color-font-light);
--text-tab-unselected: var(--color-dark-grey);
--text-toggle-label-active: var(--color-font-light);
--text-menu-link-portal: var(--color-font-dark);
--text-help-icon: var(--color-font-dark);
--text-help-panel: var(--color-font-dark);
--text-activatable-remove: var(--text-control-default);
--text-hyperlink: var(--color-font-hyperlink);
--icon-pulldown-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2l4 4 4-4' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E");
--icon-sandwich-line-portal: var(--color-font-dark);
--icon-radio-mark: var(--color-font-dark);
/* Semantic component tokens: Charts */
--surface-score-bar-track: var(--color-medium-grey);
--surface-chart-area: var(--color-light-grey);
--chart-value-positive: var(--color-signal-green);
--chart-value-neutral: var(--color-signal-yellow);
--chart-value-negative: var(--color-signal-red);
--chart-value-primary: var(--color-darkblue);
--chart-value-reference: var(--color-medium-grey);
--chart-grid-line: var(--color-medium-grey);
--chart-axis-line: var(--color-font-dark);
--chart-marker-line: var(--color-font-dark);
--chart-median-line: var(--color-font-dark);
--text-chart-default: var(--color-font-dark);
--text-score-state-positive: var(--chart-value-positive);
--text-score-state-warning: var(--chart-value-neutral);
--text-score-state-neutral: var(--text-score-state-warning);
--text-score-state-negative: var(--chart-value-negative);
--layout-score-bar-item-columns: max-content 1fr;
--layout-score-bar-item-gap: var(--spacing-large);
--layout-score-bar-item-single-score-columns: max-content 1fr max-content;
/* Semantic component tokens: Data display */
--surface-data-table: var(--color-light-grey);
--surface-data-table-header: var(--color-light-grey);
--surface-data-table-cell: var(--color-light-grey);
--surface-data-table-help-icon: var(--color-medium-grey);
--text-data-table-default: var(--color-font-dark);
--text-data-table-warning: var(--color-signal-yellow);
--text-data-table-help-icon: var(--color-font-dark);
/* Semantic component tokens: Typography */
--text-typography-preview: var(--color-font-light);
--layout-preview-align-items: flex-start;
/* Semantic pattern tokens: Portal header */
--surface-portal-header: var(--color-darkblue);
--surface-portal-header-tab: var(--color-midblue);
--surface-portal-header-tab-active: var(--color-light-grey);
--text-portal-header-brand: var(--color-font-light);
--font-size-portal-header-brand: calc(var(--font-size-brand) * 1.1);
--text-portal-header-tab: var(--color-font-light);
--text-portal-header-tab-active: var(--color-font-dark);
--layout-page-content-inset-inline: var(--card-segment-padding-horizontal);
--surface-options-row: var(--color-light-grey);
--surface-options-row-control: var(--surface-control-default);
--surface-options-row-control-selected: var(--surface-control-active);
--surface-options-row-input-clear: var(--surface-input-clear);
--surface-options-row-toggle-track: var(--surface-toggle-track);
--surface-options-row-toggle-handle: var(--surface-toggle-handle);
--surface-options-row-help-icon: var(--surface-help-icon);
--surface-options-row-help-panel: var(--surface-help-panel);
--divider-options-row-mobile: var(--color-white);
--text-options-row-default: var(--text-control-default);
--text-options-row-placeholder: var(--text-control-disabled);
--text-options-row-help-icon: var(--color-font-light);
--text-options-row-help-panel: var(--text-help-panel);
--text-options-row-description: var(--color-font-light);
--layout-options-row-margin-top: var(--spacing-small);
--layout-options-row-main-gap: var(--spacing-large);
--layout-options-row-group-gap: var(--spacing-small);
--layout-options-row-mode-toggle-width: var(--dimension-options-row-mode-toggle-width);
--layout-options-row-help-panel-width: var(--dimension-options-row-help-panel-width);
--layout-object-card-min-width: var(--dimension-object-card-min-width);
--layout-object-card-max-width: var(--dimension-object-card-max-width);
--layout-object-card-height: var(--dimension-object-card-height);
--layout-object-card-content-grow: var(--dimension-object-card-content-grow);
--layout-object-card-mobile-width: var(--dimension-object-card-mobile-width);
--layout-object-card-mobile-height: var(--dimension-object-card-mobile-height);
--layout-object-card-desktop-breakpoint: var(--dimension-object-card-desktop-breakpoint);
--layout-content-card-margin-top-desktop: var(--dimension-content-card-margin-top-desktop);
--layout-content-card-margin-top-mobile: var(--dimension-content-card-margin-top-mobile);
--dimension-notifications-card-min-width: 445px;
--layout-notifications-card-flex-basis: var(--dimension-notifications-card-min-width);
--layout-notifications-card-min-width: var(--dimension-notifications-card-min-width);
--layout-notifications-card-max-width: var(--layout-object-card-max-width);
--dimension-notifications-text-segment-fixed-height: 150px;
--dimension-notifications-text-segment-fixed-height-small: 60px;
--layout-notifications-text-segment-fixed-height: var(--dimension-notifications-text-segment-fixed-height);
--layout-notifications-text-segment-fixed-height-small: var(--dimension-notifications-text-segment-fixed-height-small);
--layout-object-group-card-min-width: var(--dimension-object-group-card-min-width);
--layout-object-group-card-max-width: var(--dimension-object-group-card-max-width);
--layout-object-group-card-height: var(--dimension-object-group-card-height);
--surface-object-card-lower-segment: var(--color-white);
--text-company-card-data-negative: var(--chart-value-negative);
--text-company-card-moat-neutral: var(--chart-value-neutral);
--layout-company-card-analysis-gap-after-moat: var(--spacing-large);
--surface-navigation-card: var(--color-white);
--surface-navigation-card-body: var(--color-white);
--surface-content-block-card-title: var(--color-light-grey);
--surface-content-block-card-content: var(--color-white);
--layout-content-cards-group-gap: var(--spacing-small);
--surface-card-list-drawer: var(--color-background-purple);
--text-content-block-card-title: var(--color-font-dark);
--text-content-block-card-content: var(--color-font-dark);
--text-card-list-drawer: var(--color-font-dark);
--layout-card-list-drawer-width: var(--dimension-card-list-drawer-width);
--surface-vsf-drawer-object-card-header: var(--color-darkblue);
--surface-vsf-drawer-object-card-body: var(--color-darkblue);
--text-vsf-drawer-object-card-heading: var(--color-font-light);
--text-vsf-drawer-object-card-body: var(--color-font-light);
--layout-vsf-drawer-object-card-column-gap: var(--spacing-large);
--text-vsf-list-card-limit-note: var(--color-signal-red);
--layout-vsf-list-card-summary-offset-block-start: var(--layout-company-card-analysis-gap-after-moat);
--layout-vsf-list-card-delete-confirmation-target-max-width: var(--layout-object-group-card-max-width);
--surface-text-layout-preview: var(--color-light-grey);
--layout-text-layout-column-gap: var(--spacing-large);
--layout-text-layout-two-column-columns: repeat(2, minmax(0, 1fr));
--layout-text-layout-three-column-columns: repeat(3, minmax(0, 1fr));
--text-align-text-layout-column-left: left;
--text-align-text-layout-column-center: center;
--text-align-text-layout-column-right: right;
--surface-delete-confirmation-overlay: var(--color-light-grey);
--surface-delete-confirmation-target-dim-overlay: var(--color-black);
--text-delete-confirmation-overlay: var(--color-font-dark);
--layout-delete-confirmation-target-max-width: 35rem;
--layout-delete-confirmation-overlay-width-factor: 0.8;
--layout-delete-confirmation-overlay-offset-block-start: 10%;
--layout-delete-confirmation-overlay-max-height: calc(100vh - 10vh);
--layout-delete-confirmation-content-gap: var(--spacing-small);
--layout-delete-confirmation-actions-gap: var(--spacing-small);
--layout-delete-confirmation-actions-offset-block-start: var(--spacing-large);
--layout-delete-confirmation-label-width: var(--dimension-input-label-width);
--layout-delete-confirmation-target-dim-opacity: 0.5;
--layer-delete-confirmation-overlay: 50;
/* Typography */
--font-family-base: "Open Sans", sans-serif;
--font-size-base: 1rem;
--font-size-small: 0.8rem;
--font-size-brand: 1.6rem;
--font-size-h1: 1.5rem;
--font-size-h2: 1.25rem;
--font-weight-regular: 400;
--font-weight-semibold: 600;
--line-height-base: 1.5;
/* Spacing */
--spacing-small: 0.3rem;
--spacing-large: 1rem;
--card-segment-padding-vertical: 0.75rem;
--card-segment-padding-horizontal: 1rem;
--card-segment-padding-horizontal-mobile: 0.5rem;
--interaction-padding-vertical: 0.25rem;
--interaction-padding-horizontal: 1rem;
/* Dimensions */
--interaction-height: 2rem;
--compact-interaction-height: 1.5rem;
--compact-interaction-padding-vertical: 0.15rem;
--compact-interaction-padding-horizontal: 0.75rem;
--small-interaction-element-size: 1.25rem;
--disabled-opacity: 0.7;
--sandwich-line-width: 1.25rem;
--sandwich-line-height: 4px;
--sandwich-line-gap: 3px;
--score-bar-height: 1rem;
--score-marker-width: 6px;
--score-marker-height: calc(var(--score-bar-height) + 2px);
--chart-height-bar: 24rem;
--chart-height-line: 18rem;
--chart-axis-label-column-width: 4rem;
--chart-axis-label-gap: 5px;
--chart-grid-line-width: 1px;
--chart-line-width: 2px;
--chart-label-position-default: 50%;
--dimension-object-card-min-width: 395px;
--dimension-object-card-max-width: 600px;
--dimension-object-card-height: 600px;
--dimension-object-card-content-grow: 1;
--dimension-object-card-mobile-width: 100%;
--dimension-object-card-mobile-height: auto;
--dimension-object-card-desktop-breakpoint: 768px;
--dimension-content-card-margin-top-desktop: 100px;
--dimension-content-card-margin-top-mobile: 1rem;
--dimension-object-group-card-min-width: 450px;
--dimension-object-group-card-max-width: 650px;
--dimension-object-group-card-height: 700px;
--dimension-input-label-width: 9rem;
--dimension-input-field-desktop-width: 400px;
--dimension-input-field-max-width: 600px;
--dimension-search-field-width: 15.3rem;
--dimension-layer-pulldown-panel: 40;
--dimension-multiselect-pulldown-panel-desktop-width: 500px;
--dimension-options-row-mode-toggle-width: 7rem;
--dimension-options-row-help-panel-width: 16rem;
--dimension-card-list-drawer-width: 40%;
--dimension-slider-track-height: 6px;
--dimension-slider-thumb-size: 22px;
--dimension-slider-thumb-offset-top: -8px;
--radius-slider-track: 999px;
/* Radius */
--radius-card: 8px;
--radius-graph-bar: 2px;
--radius-interaction: 4px;
/* Shadows */
--shadow-none: none;
--shadow-overlay: 0 10px 24px rgba(0, 0, 0, 0.22);
--shadow-interaction-inset: none;
/* Borders */
--border-none: none;
--border-control: none;
/* Runtime defaults */
--layout-object-card-shared-width: auto;
--sg-slider-progress: 0%;
--chart-label-position: var(--chart-label-position-default);
}
:root[data-portal="naurua"] {
--color-darkblue: #354A52;
--font-family-base: "Avenir", sans-serif;
}
+96
View File
@@ -0,0 +1,96 @@
/* ========================================================= */
/* Base */
/* ========================================================= */
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-base);
color: var(--color-font-dark);
background: var(--color-background-purple);
margin: 0;
padding: var(--spacing-small);
}
@media (max-width: 48rem) {
:root {
--layout-tab-navigation-large-padding-inline: 0.7rem;
}
body {
padding: var(--spacing-small);
}
}
section {
margin-bottom: var(--spacing-large);
}
section + section {
margin-top: var(--spacing-large);
}
.sg-main-heading {
margin: 0 0 var(--spacing-large) 0;
font-family: var(--font-family-base);
font-size: var(--font-size-brand);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-base);
color: var(--color-font-light);
}
.sg-sub-heading {
margin: 0 0 var(--spacing-large) 0;
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-base);
}
.sg-text-on-dark {
color: var(--color-font-light);
}
.sg-brand-title {
margin: 0 0 var(--spacing-large) 0;
font-family: var(--font-family-base);
font-size: var(--font-size-brand);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-base);
}
.sg-heading-h1 {
margin: 0 0 calc(var(--spacing-large) - 0.5rem) 0;
font-family: var(--font-family-base);
font-size: var(--font-size-h1);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-base);
}
.sg-heading-h2 {
margin: 0 0 var(--spacing-large) 0;
font-family: var(--font-family-base);
font-size: var(--font-size-h2);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-base);
}
.sg-preview-label {
margin: 0 0 var(--spacing-small) 0;
font-family: var(--font-family-base);
font-size: var(--font-size-small);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-base);
color: var(--color-font-light);
}
.sg-preview-area {
display: inline-flex;
flex-wrap: wrap;
gap: var(--spacing-small);
align-items: var(--layout-preview-align-items);
padding: 0;
border-radius: 0;
background: transparent;
}
@@ -0,0 +1,71 @@
/* ========================================================= */
/* Typography helpers */
/* ========================================================= */
.sg-body,
.sg-strong,
.sg-section-title,
.sg-bar-label,
.sg-table-label,
.sg-table-value {
font-family: var(--font-family-base);
line-height: var(--line-height-base);
}
.sg-body,
.sg-section-title {
font-size: var(--font-size-base);
font-weight: var(--font-weight-regular);
}
.sg-body {
margin: 0;
}
.sg-body:not(:last-child) {
margin: 0 0 var(--spacing-large) 0;
}
.sg-strong,
.sg-bar-label {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
}
.sg-table-label {
font-size: var(--font-size-small);
font-weight: var(--font-weight-regular);
}
.sg-table-value {
font-size: var(--font-size-small);
font-weight: var(--font-weight-semibold);
}
.sg-index a {
color: var(--color-font-hyperlink);
}
.sg-index .sg-sub-heading {
color: var(--color-font-light);
}
.sg-foundation-table {
width: 100%;
margin-bottom: var(--spacing-large);
color: var(--color-font-light);
border: 1px solid var(--color-white);
}
.sg-foundation-table th,
.sg-foundation-table td {
color: var(--color-font-light);
border: 1px solid var(--color-white);
vertical-align: top;
}
.sg-section-h2,
.sg-section-h3 {
margin: 0 0 var(--spacing-small) 0;
color: var(--color-font-light);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,90 @@
/* ========================================================= */
/* Patterns: Portal Header */
/* ========================================================= */
.sg-portal-header {
--surface-tab-selected: var(--surface-portal-header-tab);
--surface-tab-unselected: var(--surface-portal-header-tab-active);
--text-tab-selected: var(--text-portal-header-tab);
--text-tab-unselected: var(--text-portal-header-tab-active);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-large);
padding:
var(--card-segment-padding-vertical)
var(--layout-page-content-inset-inline);
border: var(--border-none);
border-radius: var(--radius-card);
box-shadow: var(--shadow-none);
background: var(--surface-portal-header);
}
.sg-portal-header--auth-segment {
flex-direction: column;
align-items: stretch;
gap: var(--spacing-small);
}
.sg-portal-header--auth-segment .sg-portal-header__auth-row {
display: flex;
justify-content: flex-end;
width: 100%;
}
.sg-portal-header__main {
display: grid;
flex: 1 1 auto;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
"brand menu"
"tabs tabs";
gap: var(--spacing-large);
min-width: 0;
}
.sg-portal-header__brand {
grid-area: brand;
margin: 0;
padding-left: 0;
font-size: var(--font-size-portal-header-brand);
color: var(--text-portal-header-brand);
}
.sg-portal-header__tabs {
grid-area: tabs;
width: 100%;
}
.sg-portal-header__menu-wrap {
grid-area: menu;
justify-self: end;
}
.sg-portal-header--auth-segment .sg-portal-header__main {
width: 100%;
}
.sg-portal-header-pattern-variant {
margin-bottom: var(--spacing-large);
}
.sg-portal-header-pattern-variant__label {
margin: 0 0 var(--spacing-small) 0;
}
.sg-portal-header-pattern-variant__next-element {
margin-top: var(--spacing-large);
}
@media (max-width: calc(var(--layout-object-card-desktop-breakpoint) - 1px)) {
.sg-portal-header__main {
gap: var(--spacing-small) var(--spacing-large);
}
}
@media (max-width: 48rem) {
.sg-portal-header__brand {
font-size: calc(var(--font-size-portal-header-brand) * 0.8);
}
}
@@ -0,0 +1,164 @@
/* ========================================================= */
/* Patterns: Options Row */
/* ========================================================= */
.sg-options-row {
--surface-control-default: var(--surface-options-row-control);
--surface-control-active: var(--surface-options-row-control-selected);
--surface-input-clear: var(--surface-options-row-input-clear);
--surface-toggle-track: var(--surface-options-row-toggle-track);
--surface-toggle-handle: var(--surface-options-row-toggle-handle);
--surface-help-icon: var(--surface-options-row-help-icon);
--surface-help-panel: var(--surface-options-row-help-panel);
--text-control-default: var(--text-options-row-default);
--text-control-disabled: var(--text-options-row-placeholder);
--text-help-icon: var(--text-options-row-help-icon);
--text-help-panel: var(--text-options-row-help-panel);
--layout-mode-toggle-width: var(--layout-options-row-mode-toggle-width);
--layout-help-panel-width: var(--layout-options-row-help-panel-width);
margin-top: var(--layout-options-row-margin-top);
display: flex;
justify-content: space-between;
gap: var(--layout-options-row-main-gap);
padding:
var(--card-segment-padding-vertical)
var(--layout-page-content-inset-inline);
border: var(--border-none);
border-radius: var(--radius-card);
box-shadow: var(--shadow-none);
background: var(--surface-options-row);
}
.sg-options-row--left-only {
justify-content: flex-start;
}
.sg-options-row__left,
.sg-options-row__right {
display: flex;
flex-wrap: wrap;
gap: var(--layout-options-row-group-gap);
align-items: center;
}
.sg-options-row__left {
justify-content: flex-start;
}
.sg-options-row__right {
justify-content: flex-end;
flex-wrap: nowrap;
margin-left: auto;
}
.sg-options-row__left > .sg-search-field-row {
flex: 0 0 auto;
}
@media (max-width: 48rem) {
.sg-options-row {
flex-direction: column;
gap: 0;
padding: 0;
}
.sg-options-row__right,
.sg-options-row__left {
padding:
var(--card-segment-padding-vertical)
var(--card-segment-padding-horizontal);
}
.sg-options-row__right {
order: 1;
justify-content: flex-end;
flex-wrap: nowrap;
margin-left: 0;
}
.sg-options-row__left {
order: 2;
box-shadow: inset 0 1px 0 var(--divider-options-row-mobile);
}
.sg-options-row--left-only {
flex-direction: row;
gap: var(--layout-options-row-main-gap);
padding:
var(--card-segment-padding-vertical)
var(--layout-page-content-inset-inline);
}
.sg-options-row--left-only .sg-options-row__left {
order: 0;
padding: 0;
box-shadow: none;
}
.sg-options-row__left > .sg-pulldown-demo,
.sg-options-row__left > .sg-input-single-line-wrap,
.sg-options-row__left > .sg-search-field-row {
flex: 1 1 calc(50% - var(--layout-options-row-group-gap));
min-width: 0;
}
.sg-options-row__left > .sg-search-field-row {
flex: 0 0 auto;
}
.sg-options-row__left > .sg-input-single-line-wrap .sg-input-single-line,
.sg-options-row__left > .sg-pulldown-demo .sg-pulldown {
width: 100%;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-pulldown-panel {
width: min(
var(--layout-pulldown-panel-form-mobile-width),
calc(100vw - (2 * var(--spacing-large)))
);
min-width: 0;
max-width: calc(100vw - (2 * var(--spacing-large)));
}
.sg-options-row .sg-pulldown-panel .sg-form-sections-card-wrapper,
.sg-options-row .sg-pulldown-panel .sg-form-sections-card {
min-width: 0;
width: 100%;
max-width: 100%;
}
.sg-pulldown-panel .sg-slider-row[data-activatable="true"] {
display: flex;
flex-wrap: wrap;
align-items: center;
column-gap: var(--spacing-large);
row-gap: var(--spacing-small);
}
.sg-pulldown-panel .sg-slider-row[data-activatable="true"] .sg-label {
min-width: 100%;
flex: 0 0 100%;
}
.sg-pulldown-panel .sg-slider-row[data-activatable="true"] .sg-slider {
min-width: 0;
max-width: 100%;
width: 100%;
flex: 1 1 0;
}
.sg-pulldown-panel .sg-slider-row[data-activatable="true"] .sg-slider-value {
min-width: calc(var(--interaction-height) * 1.25);
align-self: center;
white-space: nowrap;
flex: 0 0 auto;
}
.sg-pulldown-panel__row {
grid-template-columns: minmax(0, 1fr) var(--interaction-height);
}
.sg-pulldown-panel__label {
grid-column: 1 / -1;
}
}
@@ -0,0 +1,94 @@
/* ========================================================= */
/* Patterns: Object Card */
/* ========================================================= */
.sg-object-card-grid {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-small);
align-items: flex-start;
width: 100%;
}
.sg-object-card {
display: flex;
flex-direction: column;
flex: 1 1 var(--layout-object-card-min-width);
min-width: var(--layout-object-card-min-width);
max-width: var(--layout-object-card-max-width);
height: var(--layout-object-card-height);
}
.sg-object-card-grid.sg-object-card-grid--multi-row .sg-object-card {
flex: 0 0 var(--layout-object-card-shared-width);
width: var(--layout-object-card-shared-width);
}
@media (max-width: 767px) {
.sg-object-card {
width: var(--layout-object-card-mobile-width);
min-width: 0;
max-width: none;
height: var(--layout-object-card-mobile-height);
}
.sg-object-card[data-pattern="object-group-card"] {
flex-basis: 100%;
width: 100%;
min-width: 0;
max-width: none;
}
}
.sg-object-card__content {
flex: var(--layout-object-card-content-grow) 1 auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: var(--color-white);
}
.sg-object-card__header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.sg-object-card__actions {
display: flex;
gap: var(--spacing-small);
width: 100%;
margin-top: 0;
}
.sg-object-card__action {
flex: 1 1 0;
min-width: 0;
}
.sg-object-card--variable-height {
height: auto;
}
.sg-object-card--content-basic {
width: min(70vw, var(--layout-object-card-max-width));
margin-inline: auto;
height: auto;
}
.sg-object-card-content-basic {
margin-top: var(--layout-content-card-margin-top-desktop);
}
@media (max-width: 767px) {
.sg-object-card--content-basic {
width: var(--layout-object-card-mobile-width);
min-width: 0;
max-width: none;
}
.sg-object-card-content-basic {
margin-top: var(--layout-content-card-margin-top-mobile);
}
}
@@ -0,0 +1,76 @@
/* ========================================================= */
/* Patterns: Object Group Card */
/* ========================================================= */
.sg-object-group-card__actions {
margin-top: auto;
}
.sg-object-card[data-pattern="object-group-card"] {
flex: 1 1 var(--layout-object-group-card-min-width);
min-width: var(--layout-object-group-card-min-width);
max-width: var(--layout-object-group-card-max-width);
height: var(--layout-object-group-card-height);
}
.sg-list-group-card--height-600 .sg-object-card[data-pattern="object-group-card"] {
height: var(--layout-object-card-height);
}
@media (max-width: 767px) {
.sg-object-card[data-pattern="object-group-card"] {
height: auto;
}
.sg-list-group-card--height-600 .sg-object-card[data-pattern="object-group-card"] {
height: auto;
}
}
.sg-object-group-card__hint {
color: var(--color-font-light);
}
.sg-company-card__header-title {
display: inline-flex;
align-items: center;
gap: var(--spacing-large);
min-width: 0;
}
.sg-company-card__header-star {
display: inline-block;
flex: 0 0 auto;
inline-size: 1em;
block-size: 1em;
background: url("../assets/icons/star-filled-white.svg") no-repeat center / contain;
}
.sg-company-card__metric-negative {
color: var(--text-company-card-data-negative);
}
.sg-company-card__analysis-title {
margin: 0;
}
.sg-company-card__analysis-bars {
margin-top: 0;
}
.sg-company-card__moat-row {
display: contents;
}
.sg-company-card__moat-value {
justify-self: start;
text-align: left;
}
.sg-company-card__moat-neutral {
color: var(--text-company-card-moat-neutral);
}
.sg-company-card__summary {
margin: 0;
}
@@ -0,0 +1,4 @@
/* ========================================================= */
/* Patterns: Navigation Card */
/* ========================================================= */
@@ -0,0 +1,349 @@
/* ========================================================= */
/* Patterns: Formular mit Abschnitten */
/* ========================================================= */
.sg-form-sections-card {
--layout-form-sections-label-column-width: var(--layout-input-label-width);
--layout-form-sections-interaction-indent: calc(
var(--layout-form-sections-label-column-width) + var(--spacing-large)
);
display: flex;
flex-direction: column;
width: 100%;
background: var(--surface-form-preview);
min-width: min(
100%,
calc(var(--layout-form-sections-label-column-width) + var(--spacing-small) + var(--layout-input-field-desktop-width))
);
max-width: calc(
var(--layout-form-sections-label-column-width) + var(--spacing-small) + var(--layout-form-input-field-max-width)
);
}
.sg-form-sections-card-wrapper {
display: flex;
justify-content: center;
width: 100%;
height: auto;
}
.sg-form-sections-card__body {
padding: 0;
}
.sg-form-sections-card__title {
margin: 0 0 var(--spacing-large) 0;
}
.sg-form-sections-card__actions-segment {
margin-top: var(--spacing-large);
padding: 0;
}
.sg-form-sections-card__chapter + .sg-form-sections-card__chapter {
margin-top: var(--spacing-large);
}
.sg-form-sections-card__chapter-title,
.sg-form-sections-card__sentence {
margin: 0;
}
.sg-form-sections-card__sentence {
margin-top: var(--spacing-small);
}
.sg-form-sections-card__option-group,
.sg-form-sections-card__field-group {
display: flex;
flex-direction: column;
gap: var(--spacing-small);
margin-top: var(--spacing-large);
}
.sg-form-sections-card__field-group {
gap: var(--spacing-large);
}
.sg-form-sections-card .sg-labeled-input-row .sg-label {
min-width: var(--layout-form-sections-label-column-width);
flex: 0 0 var(--layout-form-sections-label-column-width);
margin-right: var(--spacing-large);
}
.sg-form-sections-card__option-group,
.sg-form-sections-card__actions {
padding-left: var(--layout-form-sections-interaction-indent);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-form-sections-card__chapter > :not(.sg-form-sections-card__chapter-title) + :not(.sg-form-sections-card__chapter-title) {
margin-top: var(--spacing-large);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-form-sections-card__chapter > .sg-form-sections-card__chapter-title + * {
margin-top: var(--spacing-large);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-form-sections-card__chapter > .sg-slider-row + .sg-slider-row,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-form-sections-card__chapter > .sg-checkbox-field-option + .sg-checkbox-field-option,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-form-sections-card__chapter > .sg-radio-activatable-group + .sg-radio-activatable-group {
margin-top: var(--spacing-small);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-form-sections-card__chapter > .sg-pulldown-panel__row + .sg-pulldown-panel__row {
margin-top: var(--spacing-large);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-pulldown-panel {
--sg-multiselect-label-column-width: var(--layout-multiselect-pulldown-label-column-width-fallback);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] > .sg-label,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-label,
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-pulldown-panel__label {
white-space: nowrap;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] {
display: grid;
grid-template-columns: max-content var(--sg-multiselect-label-column-width) minmax(0, 1fr) auto;
align-items: center;
column-gap: var(--spacing-large);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"].sg-slider-row--inactive-selectable {
opacity: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"].sg-slider-row--inactive-selectable .sg-slider {
opacity: var(--disabled-opacity);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"].sg-slider-row--inactive-selectable .sg-label,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"].sg-slider-row--inactive-selectable .sg-slider-value,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group[data-component-state="inactive-selectable"] > .sg-label,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group[data-component-state="inactive-selectable"] > .sg-radio-activatable-group__choices {
opacity: var(--disabled-opacity);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-mode-toggle {
grid-column: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-label {
grid-column: 2;
min-width: 0;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-slider {
grid-column: 3;
min-width: 0;
width: 100%;
max-width: none;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-slider-value {
grid-column: 4;
margin: 0;
min-width: 0;
line-height: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] {
-webkit-tap-highlight-color: transparent;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group {
display: grid;
grid-template-columns: max-content var(--sg-multiselect-label-column-width) minmax(0, 1fr);
align-items: center;
column-gap: var(--spacing-large);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-label {
grid-column: 2;
margin: 0;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-mode-toggle {
grid-column: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-radio-activatable-group__choices {
grid-column: 3;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group[data-component-state="inactive-selectable"] > .sg-radio-activatable-group__choices {
pointer-events: none;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"].sg-slider-row--inactive-selectable > :not(.sg-mode-toggle),
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group[data-component-state="inactive-selectable"] > :not(.sg-mode-toggle),
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row].sg-pulldown-panel__row--disabled > :not(.sg-mode-toggle) {
pointer-events: none;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] {
grid-template-columns: max-content var(--sg-multiselect-label-column-width) minmax(0, 1fr);
column-gap: var(--spacing-large);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-mode-toggle {
grid-column: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-pulldown-panel__label {
grid-column: 2;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-pulldown {
grid-column: 3;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row].sg-pulldown-panel__row--disabled {
opacity: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row].sg-pulldown-panel__row--disabled > .sg-pulldown-panel__label,
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row].sg-pulldown-panel__row--disabled > .sg-pulldown {
opacity: var(--disabled-opacity);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-pulldown-demo .sg-pulldown-panel {
min-width: 100%;
width: max-content;
max-width: calc(100vw - (2 * var(--spacing-large)));
}
@media (max-width: 48rem) {
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-pulldown-panel {
min-width: 100%;
width: max-content;
max-width: calc(100vw - (2 * var(--spacing-large)));
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] {
grid-template-columns: max-content minmax(0, 1fr) auto;
row-gap: var(--spacing-small);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] > .sg-label,
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-label,
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-pulldown-panel__label {
white-space: normal;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-mode-toggle {
grid-column: 1;
grid-row: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-label {
grid-column: 2;
grid-row: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-slider {
grid-column: 1 / 3;
grid-row: 2;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-slider-row[data-activatable="true"] .sg-slider-value {
grid-column: 3;
grid-row: 2;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group {
grid-template-columns: max-content minmax(0, 1fr);
row-gap: var(--spacing-small);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-label {
grid-column: 2;
grid-row: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-mode-toggle {
grid-column: 1;
grid-row: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] .sg-radio-activatable-group > .sg-radio-activatable-group__choices {
grid-column: 1 / -1;
grid-row: 2;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] {
grid-template-columns: max-content minmax(0, 1fr);
row-gap: var(--spacing-small);
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-mode-toggle {
grid-column: 1;
grid-row: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-pulldown-panel__label {
grid-column: 2;
grid-row: 1;
}
.sg-options-row[data-pattern="multiselektions-pulldown"] [data-pulldown-filter-row] > .sg-pulldown {
grid-column: 1 / -1;
grid-row: 2;
}
}
.sg-form-sections-card__actions {
display: flex;
width: 100%;
justify-content: flex-end;
gap: var(--spacing-small);
margin-top: 0;
padding-left: 0;
padding-right: 0;
}
.sg-form-sections-card__action {
width: auto;
}
.sg-navigation-card-layout {
width: 100%;
margin-top: var(--spacing-large);
}
.sg-navigation-card-block {
--surface-card: var(--surface-navigation-card);
--surface-card-body: var(--surface-navigation-card-body);
display: block;
flex: none;
min-width: 0;
max-width: none;
width: 100%;
}
.sg-navigation-card-center {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.sg-content-cards-group {
display: flex;
flex-direction: column;
gap: var(--layout-content-cards-group-gap);
}
@media (max-width: 48rem) {
.sg-form-sections-card .sg-labeled-input-row .sg-label {
min-width: 0;
flex: 0 0 auto;
margin-right: 0;
}
.sg-form-sections-card__option-group {
padding-left: 0;
}
}
@@ -0,0 +1,16 @@
/* ========================================================= */
/* Patterns: VSF List Card */
/* ========================================================= */
#layout-vsf-list-card .sg-vsf-list-card-limit-note {
margin: 0 0 var(--space-16) 0;
color: var(--text-vsf-list-card-limit-note);
}
#layout-vsf-list-card .sg-vsf-list-card__summary {
margin: var(--layout-vsf-list-card-summary-offset-block-start) 0 0 0;
}
.sg-vsf-list-card-context[data-pattern="overlay-card"] {
--layout-delete-confirmation-target-max-width: var(--layout-vsf-list-card-delete-confirmation-target-max-width);
}
@@ -0,0 +1,189 @@
/* ========================================================= */
/* Patterns: Overlay Card */
/* ========================================================= */
.sg-delete-confirmation-pattern {
width: 100%;
max-width: var(--layout-delete-confirmation-target-max-width);
}
.sg-delete-confirmation-pattern__stage {
position: relative;
overflow: visible;
}
.sg-delete-confirmation-pattern__host {
display: flex;
flex-direction: column;
}
.sg-delete-confirmation-pattern__host > .sg-delete-confirmation-pattern__target {
flex: 1 1 auto;
}
.sg-delete-confirmation-pattern__target {
position: relative;
overflow: visible;
}
.sg-delete-confirmation-pattern__target::after {
content: "";
position: absolute;
inset: 0;
background: var(--surface-delete-confirmation-target-dim-overlay);
opacity: 0;
pointer-events: none;
}
.sg-delete-confirmation-pattern__stage[data-dialog-open="true"] .sg-delete-confirmation-pattern__target::after {
opacity: var(--layout-delete-confirmation-target-dim-opacity);
pointer-events: auto;
}
.sg-delete-confirmation-pattern__floating-card {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% * var(--layout-delete-confirmation-overlay-width-factor));
box-sizing: border-box;
max-width: calc(100% * var(--layout-delete-confirmation-overlay-width-factor));
height: var(--layout-delete-confirmation-overlay-height, auto);
overflow: hidden;
z-index: var(--layer-delete-confirmation-overlay);
box-shadow: var(--shadow-overlay);
}
.sg-delete-confirmation-pattern__floating-card.sg-card.sg-card--overlay-host {
overflow: hidden;
}
.sg-delete-confirmation-pattern__floating-card[hidden] {
display: none;
}
.sg-delete-confirmation-pattern__body {
display: flex;
flex-direction: column;
gap: var(--layout-delete-confirmation-content-gap);
background: var(--surface-delete-confirmation-overlay);
}
.sg-delete-confirmation-pattern__scroll-region {
display: flex;
flex: 1 1 auto;
min-height: 0;
flex-direction: column;
overflow: auto;
}
.sg-delete-confirmation-pattern__actions-segment {
border-bottom-left-radius: var(--radius-card);
border-bottom-right-radius: var(--radius-card);
flex: 0 0 auto;
}
.sg-delete-confirmation-pattern__list {
display: grid;
gap: var(--spacing-small);
margin: 0;
margin-top: var(--spacing-large);
padding: 0;
list-style: none;
}
.sg-delete-confirmation-pattern__list-item {
margin: 0;
}
.sg-delete-confirmation-pattern__list-button {
display: inline-flex;
width: 100%;
justify-content: flex-start;
gap: var(--spacing-small);
font-weight: var(--font-weight-regular);
color: var(--text-delete-confirmation-overlay);
background: var(--surface-control-inactive);
}
.sg-delete-confirmation-pattern__list-button[data-selected="true"] {
background: var(--surface-control-active);
}
.sg-delete-confirmation-pattern__list-button[data-selected="false"] {
background: var(--surface-control-inactive);
}
.sg-delete-confirmation-pattern__list-icon {
display: block;
flex: 0 0 auto;
width: 1rem;
height: 1rem;
background: center / contain no-repeat;
}
.sg-delete-confirmation-pattern__list-button[data-selected="true"] .sg-delete-confirmation-pattern__list-icon {
background-image: url("../assets/icons/star-filled.svg");
}
.sg-delete-confirmation-pattern__list-button[data-selected="false"] .sg-delete-confirmation-pattern__list-icon {
background-image: url("../assets/icons/star-outline.svg");
}
.sg-delete-confirmation-pattern__create-list-form {
margin-top: 0;
}
.sg-delete-confirmation-pattern__create-list-segment {
gap: 0;
}
.sg-delete-confirmation-pattern__create-list-title {
margin: 0 0 var(--spacing-large) 0;
}
.sg-delete-confirmation-pattern__create-list-form[hidden] {
display: none;
}
.sg-delete-confirmation-pattern__create-list-segment[hidden] {
display: none;
}
.sg-delete-confirmation-pattern__text {
margin: 0;
color: var(--text-delete-confirmation-overlay);
}
.sg-delete-confirmation-pattern__code {
font-weight: var(--font-weight-semibold);
}
.sg-delete-confirmation-pattern__input-row {
margin: var(--spacing-large) 0 0 0;
}
.sg-delete-confirmation-pattern__actions {
display: flex;
justify-content: flex-end;
gap: var(--layout-delete-confirmation-actions-gap);
margin-top: var(--layout-delete-confirmation-actions-offset-block-start);
}
.sg-delete-confirmation-pattern__actions--footer {
margin-top: 0;
}
.sg-delete-confirmation-pattern__scroll-region > .sg-delete-confirmation-pattern__create-list-segment {
flex: 0 0 auto;
}
.sg-delete-confirmation-pattern__create-list-toggle {
width: 100%;
}
@media (max-width: 48rem) {
.sg-delete-confirmation-pattern__actions {
flex-wrap: wrap;
}
}
@@ -0,0 +1,94 @@
/* ========================================================= */
/* Patterns: Notifications */
/* ========================================================= */
.sg-notifications-pattern {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-small);
align-items: flex-start;
width: 100%;
}
.sg-notifications-pattern__card {
flex: 0 0 100%;
width: 100%;
min-width: 0;
max-width: none;
}
/* Dokumentierte Pattern-Variante: Notifications innerhalb Group Card */
.sg-notifications-pattern.sg-group-card > .sg-notifications-pattern__card {
flex: 0 0 100%;
width: 100%;
min-width: 0;
max-width: none;
}
.sg-notifications-pattern.sg-notifications-pattern--multi-row .sg-notifications-pattern__card {
flex: 0 0 100%;
width: 100%;
}
.sg-notifications-pattern__card > .sg-notifications-pattern__text-segment {
height: auto;
}
.sg-notifications-pattern__card > .sg-notifications-pattern__title-segment {
height: auto;
}
.sg-notifications-pattern__card > .sg-notifications-pattern__text-segment.sg-notifications-pattern__text-segment--small {
height: auto;
}
.sg-vsf-meldungen-layout {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--spacing-small);
align-items: start;
width: 100%;
}
.sg-vsf-meldungen-layout > .sg-group-card {
min-width: 0;
}
.sg-vsf-meldungen-mobile {
display: none;
}
.sg-vsf-meldungen-mobile__panels {
display: flex;
flex-direction: column;
gap: var(--spacing-small);
width: 100%;
}
.sg-vsf-meldungen-mobile__panel {
width: 100%;
}
@media (max-width: 767px) {
.sg-vsf-meldungen-layout {
display: none;
}
.sg-vsf-meldungen-mobile {
display: flex;
width: 100%;
}
.sg-notifications-pattern__card {
width: 100%;
min-width: 0;
max-width: none;
flex: 1 1 auto;
}
.sg-notifications-pattern__card > .sg-notifications-pattern__text-segment,
.sg-notifications-pattern__card > .sg-notifications-pattern__title-segment,
.sg-notifications-pattern__card > .sg-notifications-pattern__text-segment.sg-notifications-pattern__text-segment--small {
height: auto;
}
}
@@ -0,0 +1,87 @@
/* ========================================================= */
/* Patterns: Left Navigation */
/* ========================================================= */
.sg-left-navigation-pattern {
display: flex;
flex-direction: column;
gap: var(--spacing-small);
}
.sg-left-navigation-pattern__layout {
display: flex;
align-items: flex-start;
gap: 0;
width: 100%;
min-width: 0;
}
.sg-left-navigation-pattern__header-row {
display: none;
}
.sg-left-navigation-pattern__toggle {
display: none;
}
.sg-left-navigation-pattern__menu--collapsed {
display: none;
}
.sg-left-navigation-pattern__group-card {
min-width: 0;
}
.sg-left-navigation-pattern__group-card--navigation {
flex: 0 0 15vw;
width: 15vw;
max-width: 15vw;
padding: var(--spacing-small);
margin: 0 var(--spacing-small) var(--spacing-small) 0;
}
.sg-left-navigation-pattern__group-card--content {
flex: 1 1 auto;
}
.sg-left-navigation-pattern__group-card--content .sg-group-card__heading {
flex: 0 1 auto;
width: auto;
margin-left: 0;
}
@media (min-width: 768px) {
.sg-left-navigation-pattern__group-card--navigation .sg-tab-button-group[data-component-variant="linksmenu-items"] {
justify-content: flex-start;
}
}
@media (max-width: 767px) {
.sg-left-navigation-pattern__layout {
flex-direction: column;
}
.sg-left-navigation-pattern__header-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-small);
margin-bottom: 0;
}
.sg-left-navigation-pattern__toggle {
display: inline-flex;
flex: 0 0 auto;
}
.sg-left-navigation-pattern__group-card--navigation {
flex: 0 0 auto;
width: 100%;
max-width: none;
margin: 0;
}
.sg-left-navigation-pattern__group-card--content {
margin-top: var(--spacing-small);
}
}
@@ -0,0 +1,172 @@
/* ========================================================= */
/* Layouts: Card Listen Seite */
/* ========================================================= */
.sg-card-list-page {
display: flex;
flex-direction: column;
}
.sg-card-list-page-drawer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: var(--layout-card-list-drawer-width);
max-width: 100%;
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 220ms ease;
background: var(--surface-card-list-drawer);
color: var(--text-card-list-drawer);
box-shadow: var(--shadow-overlay);
z-index: 1000;
overflow-y: auto;
}
.sg-card-list-page-drawer[data-open="true"] {
transform: translateX(0);
}
.sg-card-list-page-drawer__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-small);
padding:
var(--card-segment-padding-vertical)
var(--layout-page-content-inset-inline);
}
.sg-card-list-page-drawer__title {
margin: 0;
color: inherit;
}
.sg-card-list-page-drawer__content {
display: flex;
flex-direction: column;
gap: var(--spacing-large);
padding:
var(--card-segment-padding-vertical)
var(--layout-page-content-inset-inline)
var(--card-segment-padding-vertical)
var(--layout-page-content-inset-inline);
}
.sg-card-list-page > * + * {
margin-top: var(--spacing-large);
}
.sg-card-list-page > .sg-options-row {
margin-top: var(--layout-options-row-margin-top);
}
.sg-card-list-page__intro-block {
margin-top: var(--spacing-large);
margin-bottom: var(--spacing-large);
}
.sg-card-list-page__intro-block .sg-text-layout-pattern__sample--sixty-width {
width: min(60vw, 100%);
}
@media (max-width: 767px) {
.sg-card-list-page-drawer {
display: none;
}
.sg-card-list-page__intro-block .sg-text-layout-pattern__sample--sixty-width {
width: 100%;
}
}
.sg-card-list-page__object-grid,
.sg-card-list-page__navigation {
width: 100%;
}
.sg-vsf-drawer-card {
--surface-card-header-primary: var(--surface-vsf-drawer-object-card-header);
--surface-card: var(--surface-vsf-drawer-object-card-body);
--surface-card-body: var(--surface-vsf-drawer-object-card-body);
--text-card-header: var(--text-vsf-drawer-object-card-heading);
--text-card-body: var(--text-vsf-drawer-object-card-body);
width: 100%;
min-width: 0;
max-width: none;
align-self: stretch;
height: auto;
}
.sg-vsf-drawer-card__content {
color: var(--text-vsf-drawer-object-card-body);
}
.sg-vsf-drawer-card .sg-text-layout-pattern__two-column,
.sg-vsf-drawer-card .sg-text-layout-pattern__column {
color: var(--text-vsf-drawer-object-card-body);
}
.sg-vsf-drawer-card .sg-text-layout-pattern__two-column {
gap: var(--layout-vsf-drawer-object-card-column-gap);
}
.sg-vsf-drawer-card__actions-segment {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: var(--spacing-large);
background: var(--surface-vsf-drawer-object-card-body);
}
.sg-vsf-drawer-card__actions-segment .sg-interaction-element.sg-button {
height: auto;
width: 100%;
}
#vsf-business-model-toggle.sg-interaction-element.sg-button {
background: var(--surface-portal-header-tab);
color: var(--text-portal-header-tab);
}
.sg-inline-controls-card {
--surface-card-body: var(--surface-card-segment-neutral);
}
.sg-card-segment.sg-inline-header-row {
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: var(--spacing-small);
}
.sg-card-segment.sg-inline-controls-row {
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: var(--spacing-small);
flex-wrap: nowrap;
}
.sg-vsf-drawer-object-card__heading {
color: var(--text-vsf-drawer-object-card-heading);
margin: 0;
}
.sg-analysis-intro-block {
display: flex;
flex-direction: column;
gap: var(--spacing-small);
}
.sg-analysis-intro-block .sg-heading-h2 {
margin: 0;
}
.sg-analysis-intro-block .sg-text-layout-pattern__sample {
margin: 0;
color: var(--text-vsf-drawer-object-card-body);
}
@@ -0,0 +1,79 @@
/* ========================================================= */
/* Patterns: Text Layouts */
/* ========================================================= */
.sg-text-layout-pattern {
display: flex;
flex-direction: column;
gap: var(--spacing-small);
}
.sg-text-layout-pattern__preview-surface {
width: 100%;
box-sizing: border-box;
padding: var(--spacing-large);
border-radius: var(--radius-card);
background: var(--surface-text-layout-preview);
}
.sg-text-layout-pattern__sample {
margin: 0;
}
.sg-text-layout-pattern__sample--full-width {
width: 100%;
}
.sg-text-layout-pattern__sample--sixty-width {
width: 60%;
}
.sg-text-layout-pattern__sample--two-column {
margin: 0;
}
.sg-text-layout-pattern__two-column {
width: 100%;
min-width: 0;
display: grid;
grid-template-columns: var(--layout-text-layout-two-column-columns);
column-gap: var(--layout-text-layout-column-gap);
}
.sg-text-layout-pattern__three-column-distributed {
width: 100%;
min-width: 0;
display: grid;
grid-template-columns: var(--layout-text-layout-three-column-columns);
column-gap: var(--layout-text-layout-column-gap);
}
.sg-text-layout-pattern__column {
margin: 0;
min-width: 0;
}
.sg-text-layout-pattern__three-column-distributed > .sg-body {
margin: 0;
}
.sg-text-layout-pattern__column--align-left {
text-align: var(--text-align-text-layout-column-left);
}
.sg-text-layout-pattern__column--align-center {
text-align: var(--text-align-text-layout-column-center);
}
.sg-text-layout-pattern__column--align-right {
text-align: var(--text-align-text-layout-column-right);
}
@media (max-width: 767px) {
.sg-text-layout-pattern__sample--sixty-width,
.sg-text-layout-pattern__sample--two-column,
.sg-text-layout-pattern__two-column,
.sg-text-layout-pattern__three-column-distributed {
width: 100%;
}
}
@@ -0,0 +1,47 @@
/* ========================================================= */
/* Patterns: Card Gruppe mit Tastennavigation */
/* ========================================================= */
.sg-content-block-card-group {
display: flex;
flex-direction: column;
gap: var(--spacing-small);
width: 100%;
}
.sg-content-block-card-group[hidden] {
display: none;
}
.sg-content-block-card-group__tabs,
.sg-content-block-card-group__panels,
.sg-content-block-card-group__panel,
.sg-content-block-card-group__card {
width: 100%;
}
.sg-content-block-card-group__panel[hidden] {
display: none;
}
.sg-content-block-card-group__title {
color: var(--text-content-block-card-title);
background: var(--surface-content-block-card-title);
}
.sg-content-block-card-group__content {
color: var(--text-content-block-card-content);
background: var(--surface-content-block-card-content);
}
.sg-content-block-card-group__content .sg-body {
margin: 0;
}
@media (max-width: 767px) {
.sg-content-block-card-group__tabs[role="tablist"] > [role="tab"] {
flex: 1 1 auto;
min-width: max-content;
white-space: nowrap;
}
}
@@ -0,0 +1,96 @@
/* ========================================================= */
/* Layouts: VSF List Detailseite */
/* ========================================================= */
.sg-vsf-list-detail-page {
display: flex;
flex-direction: column;
}
.sg-vsf-list-detail-page > * + * {
margin-top: var(--spacing-large);
}
.sg-vsf-list-detail-page__content {
display: flex;
gap: var(--spacing-small);
width: 100%;
}
.sg-vsf-list-detail-page__mobile-tabs {
display: none;
}
.sg-vsf-list-detail-page__left-column {
width: 30%;
min-width: 30%;
}
.sg-vsf-list-detail-page__right-column {
width: 70%;
min-width: 0;
}
.sg-vsf-list-detail-page__left-column .sg-group-card {
display: flex;
flex-direction: column;
width: 100%;
}
.sg-vsf-list-detail-page__left-column .sg-group-card > .sg-card {
flex: 0 0 100%;
width: 100%;
}
.sg-vsf-list-detail-page__notification-card.sg-vsf-list-detail-page__notification-card--hidden {
display: none;
}
.sg-vsf-list-detail-page__meldungen-overlay-pattern {
max-width: 100%;
}
.sg-vsf-list-detail-page__meldungen-overlay-pattern .sg-delete-confirmation-pattern__floating-card {
top: calc(var(--spacing-large) * 5);
transform: translateX(-50%);
}
.sg-vsf-list-detail-page__mobile-toggle {
display: none;
}
@media (max-width: 767px) {
.sg-vsf-list-detail-page__mobile-tabs {
display: flex;
width: 100%;
margin-bottom: 0;
}
.sg-vsf-list-detail-page__content {
display: block;
margin-top: var(--spacing-small);
}
.sg-vsf-list-detail-page__left-column {
width: 100%;
min-width: 0;
}
.sg-vsf-list-detail-page__right-column {
width: 100%;
}
.sg-vsf-list-detail-page__content > [data-vsf-list-detail-panel] {
width: 100%;
}
.sg-vsf-list-detail-page__content > [data-vsf-list-detail-panel][hidden] {
display: none;
}
.sg-vsf-list-detail-page__mobile-toggle {
display: inline-flex;
width: 100%;
margin-bottom: var(--spacing-large);
}
}
@@ -0,0 +1,55 @@
/* ========================================================= */
/* Layouts: VSF Listen Übersicht Seite V2 */
/* ========================================================= */
.sg-vsf-list-overview-page-v2 {
display: flex;
flex-direction: column;
}
.sg-vsf-list-overview-page-v2 > * + * {
margin-top: var(--spacing-large);
}
.sg-vsf-list-overview-page-v2 .sg-portal-header-pattern-variant {
margin-bottom: 0;
}
.sg-vsf-list-overview-page-v2 > .sg-vsf-list-overview-page-v2__intro {
margin-top: var(--spacing-large);
}
.sg-vsf-list-overview-page-v2__content {
display: flex;
gap: var(--spacing-small);
width: 100%;
}
.sg-vsf-list-overview-page-v2__primary {
width: 70%;
min-width: 0;
}
.sg-vsf-list-overview-page-v2__secondary {
width: 30%;
min-width: 0;
}
.sg-vsf-list-overview-page-v2__intro .sg-text-layout-pattern__sample--sixty-width {
color: var(--color-font-light);
}
@media (max-width: 767px) {
.sg-vsf-list-overview-page-v2__content {
display: block;
}
.sg-vsf-list-overview-page-v2__primary,
.sg-vsf-list-overview-page-v2__secondary {
width: 100%;
}
.sg-vsf-list-overview-page-v2__secondary {
margin-top: var(--spacing-large);
}
}
@@ -0,0 +1,29 @@
/* ========================================================= */
/* Layouts: VSF Register Step 1 */
/* ========================================================= */
.sg-vsf-register-step-1 {
display: block;
margin-top: clamp(32px, 6vw, 100px);
padding: 0 var(--layout-page-content-inset-inline);
box-sizing: border-box;
}
.sg-vsf-register-step-1-page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.sg-vsf-register-step-1__card {
width: 100%;
max-width: var(--layout-object-card-max-width);
margin: 0 auto;
}
.sg-vsf-register-step-1__social-links {
display: flex;
justify-content: center;
gap: var(--spacing-large);
margin-top: var(--spacing-large);
}
@@ -0,0 +1,8 @@
/* ========================================================= */
/* Layouts: Page Layout App */
/* ========================================================= */
.sg-page-layout-app__heading-block {
margin-top: var(--spacing-large);
margin-bottom: var(--spacing-large);
}
@@ -0,0 +1,33 @@
/* ========================================================= */
/* Layouts: Page Layout Public */
/* ========================================================= */
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
display: flex;
flex-direction: column;
}
.sg-page-layout-public__heading-block {
margin-top: var(--spacing-large);
margin-bottom: var(--spacing-large);
}
.sg-page-layout-public__content-mirror {
width: 70vw;
max-width: 1100px;
margin-inline: auto;
display: flex;
flex-direction: column;
flex: 1;
}
.sg-page-layout-public__footer-card {
margin-top: auto;
}

Some files were not shown because too many files have changed in this diff Show More