From a2f6daed1c9d7fc198a11f46082d400721c2762c Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Fri, 20 Mar 2026 16:46:40 +0100 Subject: [PATCH] docs: Update UI Guidelines --- UI_GUIDELINES.md | 200 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 191 insertions(+), 9 deletions(-) diff --git a/UI_GUIDELINES.md b/UI_GUIDELINES.md index 20f2558..1111591 100644 --- a/UI_GUIDELINES.md +++ b/UI_GUIDELINES.md @@ -2,6 +2,7 @@ > Gilt für alle Views unter `src/main/java/de/assecutor/votianlt/pages/view/` > Theme-Datei: `src/main/frontend/themes/votian-modern/styles.css` +> Stand: UI-Änderungen bis 20.03.2026 berücksichtigt (`ViewToolbar`-Migrationen, Landing-/Dashboard-Updates, TabSheet-Dialoge, Message-/Statistics-Layouts) --- @@ -159,8 +160,8 @@ Jede View muss **genau eine** der folgenden Root-Klassen tragen: | `statistics-chat-view` | Statistik/Chat | `VerticalLayout` | | `dashboard-view` | Dashboard-Seiten | `VerticalLayout` | | `dashboard-home-view` | Haupt-Dashboard (transparenter Hintergrund) | `VerticalLayout` | -| `landing-view` | Startseite (unauthentifiziert, weißer Hintergrund) | `Main` | -| `login-view` | Login-Seite (weißer Hintergrund, fullscreen) | `Main` | +| `landing-view` | Startseite (Shell-Gradient im Body, weiße Inhalts-Panels) | `Main` | +| `login-view` | Login-Seite (Shell-Gradient, einspaltig, fullscreen) | `Main` | ### Kritische Java-Regeln @@ -243,6 +244,11 @@ Einheitliche Toolbar für alle Seiten-Überschriften: add(new ViewToolbar(getTranslation("my.view.title"))); // Mit Aktionsbutton: add(new ViewToolbar(getTranslation("my.view.title"), myButton)); +// Mit gruppierten Sekundäraktionen: +add(new ViewToolbar( + getTranslation("my.view.title"), + ViewToolbar.group(backButton, exportButton), + addButton)); ``` ```css @@ -251,13 +257,27 @@ add(new ViewToolbar(getTranslation("my.view.title"), myButton)); padding: 0.75rem 1rem; box-sizing: border-box; } +.view-toolbar-title-row { + gap: 0.8rem; +} .view-toolbar-title { color: var(--app-text-strong); font-weight: 800; letter-spacing: -0.05em; } +.view-toolbar-actions, +.view-toolbar-group { + width: 100%; + justify-content: flex-end; +} ``` +**Regeln**: +- `ViewToolbar` ist Standard für neue Header und ersetzt ad-hoc `HorizontalLayout`+`H2`/`H1`-Konstrukte. +- Bereits migriert: `EditProfileView`, `ImprintView`, `StatisticsView`, `UserMessagesView`, `MessageDetailsView`. +- Titel im Toolbar-Header bleiben einzeilig (`white-space: nowrap` wird im Component-Code gesetzt). +- Ausnahme: Für komplexe Header-Zeilen wie in `MessageDetailsView` darf der Inhalt einer `ViewToolbar` ersetzt werden, die Basiskomponente bleibt aber `ViewToolbar`. + --- ## 8. Daten-Grids @@ -289,6 +309,7 @@ add(panel); - Hintergrund: `rgba(255,255,255,0.88)`, `border-radius: 24px` - Header: Gedämpftes Grau, Uppercase, `font-weight: 800`, `font-size: 0.76rem` - Selektierte Zeile: `--app-accent-soft` Hintergrund +- Auch eingebettete Grids in Formular-/Summary-Bereichen werden so gekapselt; kein nacktes `Grid` direkt in einen Content-Abschnitt hängen. --- @@ -377,6 +398,17 @@ body:has(.dashboard-home-view) { } ``` +**Aktuelles Strukturmuster**: +- Das eingeloggte Dashboard besteht aus Hero + Systemsektion; App-Overview und Footer bleiben auf der öffentlichen Landing Page. +- Dashboard-Feature-Karten dürfen vollständig klickbar sein, indem `feature-card` in `RouterLink.feature-card-link` gewrappt wird. + +```java +RouterLink link = new RouterLink(); +link.setRoute(ShowJobsView.class); +link.add(card); +link.addClassName("feature-card-link"); +``` + --- ## 12. Sidebar / Navigation @@ -406,8 +438,21 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard")); transform: translateX(4px); background: rgba(255,255,255,0.14); } +.app-nav-row-root { + width: 20.5rem; +} +.app-nav-row-management-child { + width: 18.25rem; +} +.app-nav-row-user-child { + width: 17rem; +} ``` +**Zusatzregeln**: +- Root-Einträge und Kind-Einträge bekommen unterschiedliche Breitenklassen entsprechend ihrer Ebene im `TreeGrid`. +- Drawer-Labels werden im Renderer mit `white-space: nowrap` versehen; Zeilenumbrüche in der Navigation sind nicht vorgesehen. + ### User Menu ```css @@ -433,7 +478,7 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard")); | `simple-card` | 26px | rgba(255,255,255,0.88) | Einfache Karten | | `detail-card` | 24px | rgba(255,255,255,0.88) | Detail-Informationen | | `hero-panel` | 34px | Dunkelblau-Gradient | Hero-Bereiche | -| `feature-card` | 24px | Linear-Gradient weiß/blau | Feature-Kacheln | +| `feature-card` | 24px | Linear-Gradient weiß/blau | Feature-Kacheln, optional klickbar via `feature-card-link` | | `message-card` | 22px | Linear-Gradient weiß | Nachrichten | | `station-tile` | 24px | Linear-Gradient weiß | Station-Kacheln | | `job-task-card` | 22px | Linear-Gradient weiß | Aufgaben-Karten | @@ -561,6 +606,49 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard")); ## 17. Message & Chat Components +### Header & Thread Layout + +Message-Views verwenden `ViewToolbar` als erstes sichtbares Header-Element. Eigene `.message-thread-header`-Layouts sollen für neue Implementierungen nicht mehr aufgebaut werden. + +```java +addClassName("message-hub-view"); + +VerticalLayout contentLayout = new VerticalLayout(); +contentLayout.setPadding(true); +contentLayout.setSpacing(true); +contentLayout.setWidthFull(); +contentLayout.setHeightFull(); +contentLayout.addClassName("message-thread-layout"); + +contentLayout.add(new ViewToolbar(getTranslation("usermessages.title.with", clientName))); +``` + +```css +.message-thread-layout { + width: 100%; + margin: 0; + padding: 0; + background: transparent; + border: none; + box-shadow: none; +} +.message-thread-scroller { + border: 1px solid var(--app-border-strong); + background: linear-gradient(180deg, rgba(249,250,251,0.95), rgba(243,244,246,0.88)); + border-radius: 24px; + overflow-y: auto !important; +} +.message-thread { + padding: 1rem !important; +} +.message-thread-input { + border: 1px solid var(--app-border-strong); + background: rgba(255,255,255,0.82); + border-radius: 22px; + padding: 0.85rem 1rem; +} +``` + ### Message Card ```css @@ -588,8 +676,8 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard")); box-shadow: var(--app-shadow-sm); } .message-bubble.client { - background: rgba(255,255,255,0.96); - border: 1px solid var(--app-border-strong); + background: linear-gradient(135deg, rgba(255,255,255,0.98), rgba(241,245,249,0.92)); + border: 1px solid rgba(148,163,184,0.25); } .message-bubble.server { background: linear-gradient(135deg, rgba(191,219,254,0.8), rgba(167,243,208,0.74)); @@ -619,7 +707,8 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard")); ## 18. Dialoge ```css -vaadin-dialog-overlay::part(content) { +vaadin-dialog-overlay::part(content), +vaadin-confirm-dialog-overlay::part(content) { border-radius: 28px; border: 1px solid var(--app-border-strong); background: rgba(255,255,255,0.94); @@ -639,13 +728,76 @@ vaadin-dialog-overlay::part(content) { ```css .dialog-form-panel, -.dialog-content-panel { +.dialog-content-panel, +.dialog-cargo-card { backdrop-filter: blur(12px); padding: 1rem; border-radius: 24px; } + +.dialog-task-card { + transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; +} ``` +### Station Dialoge mit TabSheet und innerer Karte + +`PickupStationDialog` und `DeliveryStationDialog` verwenden nicht mehr die Standard-Overlay-Karte, sondern ein transparentes Overlay mit eigener weißer Innenkarte. + +```java +getElement().setAttribute("theme", "no-inner-card"); + +TabSheet tabSheet = new TabSheet(); +tabSheet.setWidthFull(); +tabSheet.setSizeFull(); + +Div frame = new Div(); +frame.getStyle().set("border", "10px solid transparent"); +frame.getStyle().set("display", "flex"); +frame.getStyle().set("flex-direction", "column"); +frame.getStyle().set("flex", "1"); +frame.setSizeFull(); + +Div whiteCard = new Div(); +whiteCard.getStyle().set("background", "white"); +whiteCard.getStyle().set("border-radius", "24px"); +whiteCard.getStyle().set("flex", "1"); +whiteCard.getStyle().set("overflow", "auto"); +whiteCard.setSizeFull(); +whiteCard.add(tabSheet); + +frame.add(whiteCard); +add(frame); +``` + +```css +vaadin-dialog-overlay[theme~="no-inner-card"]::part(content) { + border-radius: 0; + border: none; + background: none; + backdrop-filter: none; + box-shadow: none; + padding: 0; + margin: 0; +} + +vaadin-tabsheet::part(content) { + border-radius: 0 0 24px 24px; + background: rgba(255,255,255,0.86); +} + +[part="tabs-container"] { + background: white; + border-radius: 24px 24px 0 0; + border-bottom: 1px solid #e0e0e0; +} +``` + +**Tab-Konventionen**: +- `PickupStationDialog`: Adresse, Termine, Fracht +- `DeliveryStationDialog`: Adresse, Aufgaben +- Fehlerindikatoren sitzen direkt am jeweiligen `Tab` + --- ## 19. Detail Cards @@ -758,12 +910,24 @@ loginShell.addClassName("login-shell"); .landing-view { padding: 20px; gap: 20px; - background: #ffffff !important; + background: transparent !important; min-height: 100vh; min-height: 100dvh; } body:has(.landing-view) { - background: #ffffff; + background: var(--app-shell-background); +} +.landing-view .surface-panel { + border: 1px solid var(--app-border-strong); + background: transparent; + backdrop-filter: none; + box-shadow: none; +} +.landing-view .landing-header, +.landing-view .section-panel, +.landing-view .app-overview-panel, +.landing-view .footer-panel { + background: var(--app-surface-solid); } ``` @@ -786,6 +950,23 @@ body:has(.landing-view) { } ``` +### App-Section vs. Footer + +CTA-Text und Slogan liegen auf der Landing Page jetzt im `app-overview-panel`, nicht mehr im Footer. + +```css +.app-cta { + margin-top: 0.75rem; + color: var(--app-accent-strong); + font-weight: 700; + max-width: 820px; +} +.app-slogan { + color: var(--app-accent-strong); + font-style: italic; +} +``` + ### Hero Panel ```css @@ -1136,3 +1317,4 @@ Beim Erstellen einer neuen View folgende Punkte prüfen: - [ ] Schmale Formular-Container haben `setWidth("Xpx")` **und** `setMaxWidth("100%")` - [ ] Inline-Styles nur für dynamische Werte verwenden, alles andere per CSS-Klassen - [ ] Sekundäre Buttons (`Abbrechen`, `Zurück`, `Export`) haben `LUMO_TERTIARY`, Hauptaktionen haben `LUMO_PRIMARY` +- [ ] Tabbed Dialoge verwenden das `no-inner-card`-Pattern mit innerer weißer Karte statt zusätzlicher Overlay-Dekoration