docs: Update UI Guidelines

This commit is contained in:
2026-03-20 16:46:40 +01:00
parent 08ece158df
commit a2f6daed1c

View File

@@ -2,6 +2,7 @@
> Gilt für alle Views unter `src/main/java/de/assecutor/votianlt/pages/view/` > Gilt für alle Views unter `src/main/java/de/assecutor/votianlt/pages/view/`
> Theme-Datei: `src/main/frontend/themes/votian-modern/styles.css` > 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` | | `statistics-chat-view` | Statistik/Chat | `VerticalLayout` |
| `dashboard-view` | Dashboard-Seiten | `VerticalLayout` | | `dashboard-view` | Dashboard-Seiten | `VerticalLayout` |
| `dashboard-home-view` | Haupt-Dashboard (transparenter Hintergrund) | `VerticalLayout` | | `dashboard-home-view` | Haupt-Dashboard (transparenter Hintergrund) | `VerticalLayout` |
| `landing-view` | Startseite (unauthentifiziert, weißer Hintergrund) | `Main` | | `landing-view` | Startseite (Shell-Gradient im Body, weiße Inhalts-Panels) | `Main` |
| `login-view` | Login-Seite (weißer Hintergrund, fullscreen) | `Main` | | `login-view` | Login-Seite (Shell-Gradient, einspaltig, fullscreen) | `Main` |
### Kritische Java-Regeln ### Kritische Java-Regeln
@@ -243,6 +244,11 @@ Einheitliche Toolbar für alle Seiten-Überschriften:
add(new ViewToolbar(getTranslation("my.view.title"))); add(new ViewToolbar(getTranslation("my.view.title")));
// Mit Aktionsbutton: // Mit Aktionsbutton:
add(new ViewToolbar(getTranslation("my.view.title"), myButton)); 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 ```css
@@ -251,13 +257,27 @@ add(new ViewToolbar(getTranslation("my.view.title"), myButton));
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
box-sizing: border-box; box-sizing: border-box;
} }
.view-toolbar-title-row {
gap: 0.8rem;
}
.view-toolbar-title { .view-toolbar-title {
color: var(--app-text-strong); color: var(--app-text-strong);
font-weight: 800; font-weight: 800;
letter-spacing: -0.05em; 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 ## 8. Daten-Grids
@@ -289,6 +309,7 @@ add(panel);
- Hintergrund: `rgba(255,255,255,0.88)`, `border-radius: 24px` - Hintergrund: `rgba(255,255,255,0.88)`, `border-radius: 24px`
- Header: Gedämpftes Grau, Uppercase, `font-weight: 800`, `font-size: 0.76rem` - Header: Gedämpftes Grau, Uppercase, `font-weight: 800`, `font-size: 0.76rem`
- Selektierte Zeile: `--app-accent-soft` Hintergrund - 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 ## 12. Sidebar / Navigation
@@ -406,8 +438,21 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard"));
transform: translateX(4px); transform: translateX(4px);
background: rgba(255,255,255,0.14); 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 ### User Menu
```css ```css
@@ -433,7 +478,7 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard"));
| `simple-card` | 26px | rgba(255,255,255,0.88) | Einfache Karten | | `simple-card` | 26px | rgba(255,255,255,0.88) | Einfache Karten |
| `detail-card` | 24px | rgba(255,255,255,0.88) | Detail-Informationen | | `detail-card` | 24px | rgba(255,255,255,0.88) | Detail-Informationen |
| `hero-panel` | 34px | Dunkelblau-Gradient | Hero-Bereiche | | `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 | | `message-card` | 22px | Linear-Gradient weiß | Nachrichten |
| `station-tile` | 24px | Linear-Gradient weiß | Station-Kacheln | | `station-tile` | 24px | Linear-Gradient weiß | Station-Kacheln |
| `job-task-card` | 22px | Linear-Gradient weiß | Aufgaben-Karten | | `job-task-card` | 22px | Linear-Gradient weiß | Aufgaben-Karten |
@@ -561,6 +606,49 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard"));
## 17. Message & Chat Components ## 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 ### Message Card
```css ```css
@@ -588,8 +676,8 @@ header.addClickListener(event -> UI.getCurrent().navigate("dashboard"));
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
} }
.message-bubble.client { .message-bubble.client {
background: rgba(255,255,255,0.96); background: linear-gradient(135deg, rgba(255,255,255,0.98), rgba(241,245,249,0.92));
border: 1px solid var(--app-border-strong); border: 1px solid rgba(148,163,184,0.25);
} }
.message-bubble.server { .message-bubble.server {
background: linear-gradient(135deg, rgba(191,219,254,0.8), rgba(167,243,208,0.74)); 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 ## 18. Dialoge
```css ```css
vaadin-dialog-overlay::part(content) { vaadin-dialog-overlay::part(content),
vaadin-confirm-dialog-overlay::part(content) {
border-radius: 28px; border-radius: 28px;
border: 1px solid var(--app-border-strong); border: 1px solid var(--app-border-strong);
background: rgba(255,255,255,0.94); background: rgba(255,255,255,0.94);
@@ -639,13 +728,76 @@ vaadin-dialog-overlay::part(content) {
```css ```css
.dialog-form-panel, .dialog-form-panel,
.dialog-content-panel { .dialog-content-panel,
.dialog-cargo-card {
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
padding: 1rem; padding: 1rem;
border-radius: 24px; 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 ## 19. Detail Cards
@@ -758,12 +910,24 @@ loginShell.addClassName("login-shell");
.landing-view { .landing-view {
padding: 20px; padding: 20px;
gap: 20px; gap: 20px;
background: #ffffff !important; background: transparent !important;
min-height: 100vh; min-height: 100vh;
min-height: 100dvh; min-height: 100dvh;
} }
body:has(.landing-view) { 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 ### Hero Panel
```css ```css
@@ -1136,3 +1317,4 @@ Beim Erstellen einer neuen View folgende Punkte prüfen:
- [ ] Schmale Formular-Container haben `setWidth("Xpx")` **und** `setMaxWidth("100%")` - [ ] Schmale Formular-Container haben `setWidth("Xpx")` **und** `setMaxWidth("100%")`
- [ ] Inline-Styles nur für dynamische Werte verwenden, alles andere per CSS-Klassen - [ ] 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` - [ ] 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