docs: Update UI Guidelines
This commit is contained in:
200
UI_GUIDELINES.md
200
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
|
||||
|
||||
Reference in New Issue
Block a user