24 KiB
VotianLT – UI Theme & Gestaltungsrichtlinien
Gilt für alle Views unter
src/main/java/de/assecutor/votianlt/pages/view/Theme-Datei:src/main/frontend/themes/votian-modern/styles.css
1. Design-Prinzipien
- Konsistenz: Alle Views nutzen dieselben Klassen, Abstände und Farben.
- Kein Overflow: Kein View darf horizontal scrollen. Alle Container haben
box-sizing: border-box,max-width: 100%undmin-width: 0. - Kein externes Margin/Padding am View-Root: Views füllen ihren Container vollständig aus. Abstände zum Bildschirmrand entstehen ausschließlich durch den
view-container. - Lumo-Overrides: Vaadin-Lumo-Standardstile werden durch gezielte CSS-Regeln mit höherer Spezifizität überschrieben – nicht mit inline Styles in Java-Code lösen.
2. Farben & Custom Properties
Alle Farben sind als CSS-Custom-Properties auf html definiert und müssen statt hartcodierter Hex-Werte verwendet werden.
Hauptfarben
| Variable | Wert | Verwendung |
|---|---|---|
--app-accent |
#2563eb |
Primäraktionen, Links, Highlights |
--app-accent-strong |
#1d4ed8 |
Hover-Zustand von Primärbuttons |
--app-accent-soft |
rgba(37,99,235,0.12) |
Selektierter Zeilenhintergrund |
--app-success |
#059669 |
Erfolgsstatus, abgeschlossene Aufgaben |
--app-warning |
#d97706 |
Warnungen |
--app-danger |
#dc2626 |
Fehler, kritische Zustände |
Oberflächen & Hintergründe
| Variable | Wert | Verwendung |
|---|---|---|
--app-surface |
rgba(255,255,255,0.78) |
Karten, Panels (Glasmorphismus) |
--app-surface-solid |
#ffffff |
Volldeckende Flächen |
--app-surface-muted |
rgba(247,250,255,0.88) |
Gedämpfte Panels |
--app-shell-background |
Radial+Linear-Gradient | Seitenhintergrund (Body) |
--app-sidebar-background |
Linear-Gradient dunkelblau | Drawer/Sidebar |
Text
| Variable | Wert | Verwendung |
|---|---|---|
--app-text-strong |
#0f172a |
Überschriften, wichtige Labels |
--app-text |
#1e293b |
Fließtext |
--app-text-muted |
#64748b |
Sekundärtext, Beschreibungen |
Ränder
| Variable | Wert | Verwendung |
|---|---|---|
--app-border |
rgba(148,163,184,0.18) |
Subtile Trennlinien |
--app-border-strong |
rgba(148,163,184,0.28) |
Sichtbare Kartenränder |
--app-field-border |
#d6dde7 |
Formularfeld-Rand |
--app-field-border-hover |
#c6d0dd |
Formularfeld-Rand (Hover) |
Schatten
| Variable | Wert | Verwendung |
|---|---|---|
--app-shadow-sm |
0 12px 28px rgba(15,23,42,0.08) |
Kleine Karten |
--app-shadow-md |
0 20px 44px rgba(15,23,42,0.10) |
Mittlere Panels |
--app-shadow-lg |
0 28px 72px rgba(15,23,42,0.12) |
Surface-Panels |
--app-shadow-xl |
0 36px 88px rgba(8,18,36,0.24) |
Hero-Bereiche, Dialoge |
3. Typografie
Schriftart: Manrope (Google Fonts, Gewichte 400–800)
Fallbacks: Avenir Next, Segoe UI, sans-serif
Lumo-Überschreibungen:
--lumo-font-family: "Manrope", "Avenir Next", "Segoe UI", sans-serif;
--lumo-base-color: #f5f7fb;
Alle h1–h6 haben letter-spacing: -0.03em und color: var(--app-text-strong).
Responsive Schriftgrößen (clamp)
| Element | Wert |
|---|---|
.form-title |
clamp(1.55rem, 3vw, 2.2rem), font-weight 800 |
.section-title |
clamp(1.4rem, 3.4vw, 2rem), font-weight 800 |
.hero-panel-title |
clamp(2rem, 5vw, 3.4rem), font-weight 800 |
.dashboard-title |
clamp(1.7rem, 3.2vw, 2.5rem), font-weight 800 |
.login-card-title |
clamp(1.8rem, 4vw, 2.4rem), font-weight 800 |
Regel: Überschriften mit font-weight: 800 und letter-spacing: -0.05em bis -0.06em für Großtitel.
4. Layout-Struktur (App Shell)
vaadin-app-layout (336px Drawer)
├── [part=drawer] ← Sidebar (Dunkelblau-Gradient)
│ ├── .app-drawer-header
│ ├── .app-drawer-scroll (Scroller, flex: 1)
│ │ └── .app-nav-container
│ │ └── .app-nav-tree (TreeGrid)
│ └── .app-user-menu
└── [Light DOM default slot]
└── .view-container ← Routed View Container
└── <geroutete View>
view-container
Der Container, in dem alle gerouteten Views dargestellt werden:
.view-container {
display: flex;
flex-direction: column;
margin: 20px; /* Abstand zum Browserrand */
width: calc(100% - 40px);
height: calc(100dvh - 40px);
overflow: hidden;
border-radius: 12px;
box-sizing: border-box;
}
Wichtig: Views, die in .view-container landen, erhalten automatisch:
flex: 1 1 0; min-height: 0; min-width: 0; max-width: 100%; box-sizing: border-box;
5. View-Klassen
Jede View muss genau eine der folgenden Root-Klassen tragen:
| Klasse | Verwendung | Extends |
|---|---|---|
data-view |
Listen, Tabellen, Grids | Main oder VerticalLayout |
form-page |
Formulare mit zentriertem Inhalt | VerticalLayout |
admin-form-view |
Admin-Formulare | VerticalLayout |
message-hub-view |
Nachrichten-Übersicht | Main |
statistics-chat-view |
Statistik/Chat | VerticalLayout |
dashboard-view |
Dashboard-Seiten | VerticalLayout |
landing-view |
Startseite (unauthentifiziert) | Main |
login-view |
Login-Seite | Main |
Kritische Java-Regeln
// ✅ Richtig für Daten-Views:
addClassName("data-view");
// KEIN setJustifyContentMode(CENTER) — wird per CSS auf START erzwungen
// ✅ Richtig für Formular-Views (zentrierter Inhalt):
addClassName("form-page");
setJustifyContentMode(JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(Alignment.CENTER);
// ❌ Falsch: setPadding(true) auf View-Root oder form-shell ohne form-card
// → wird per CSS auf 0 zurückgesetzt, erzeugt keine visuelle Wirkung
6. Interne Layout-Klassen
form-shell – Vollbreite-Wrapper
VerticalLayout layout = new VerticalLayout();
layout.setPadding(false);
layout.setSpacing(true);
layout.setWidthFull();
layout.addClassName("form-shell");
Regeln:
- Kein
setPadding(true)→ hat keine Wirkung, da via CSS überschrieben - Kein horizontales Margin
box-sizing: border-box,max-width: 100%,min-width: 0
form-card – Schmales zentriertes Karten-Formular
container.addClassNames("form-shell", "narrow", "form-card");
container.setWidth("400px");
container.setMaxWidth("100%");
Bekommt weißen Hintergrund (0.9 Alpha), border-radius: 28px, backdrop-filter: blur(18px).
surface-panel – Datenpanel / Tabellenkarte
Div panel = new Div(grid);
panel.addClassNames("surface-panel", "data-grid-panel");
panel.setWidthFull();
.surface-panel {
border: 1px solid var(--app-border-strong);
background: var(--app-surface);
backdrop-filter: blur(18px);
box-shadow: var(--app-shadow-lg);
border-radius: 28px;
box-sizing: border-box;
max-width: 100%;
}
.data-grid-panel {
padding: 0.8rem;
min-height: 420px;
box-sizing: border-box;
max-width: 100%;
}
7. ViewToolbar
Einheitliche Toolbar für alle Seiten-Überschriften:
add(new ViewToolbar(getTranslation("my.view.title")));
// Mit Aktionsbutton:
add(new ViewToolbar(getTranslation("my.view.title"), myButton));
.view-toolbar {
width: 100%;
padding: 0.75rem 1rem;
box-sizing: border-box;
}
.view-toolbar-title {
color: var(--app-text-strong);
font-weight: 800;
letter-spacing: -0.05em;
}
8. Daten-Grids
Standard-Muster für alle Tabellen-Views:
// View-Root
setSizeFull();
addClassName("data-view");
// ViewToolbar
add(new ViewToolbar(getTranslation("title"), addButton));
// Grid
Grid<T> grid = new Grid<>(T.class, false);
grid.setWidthFull();
grid.setHeightFull();
grid.addClassName("data-grid");
// Panel-Wrapper
Div panel = new Div(grid);
panel.addClassNames("surface-panel", "data-grid-panel");
panel.setWidthFull();
add(panel);
Grid-Styling:
- 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-softHintergrund
9. Formulare (Form-Views)
// View-Root
setSizeFull();
setJustifyContentMode(JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(Alignment.CENTER);
setPadding(true);
addClassName("form-page");
// Formular-Container
VerticalLayout container = new VerticalLayout();
container.setWidth("600px");
container.setMaxWidth("100%");
container.setPadding(true);
container.setSpacing(true);
container.addClassNames("form-shell", "form-card");
// Formular-Felder
FormLayout form = new FormLayout();
form.setWidthFull();
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
Formularfelder haben automatisch:
border-radius: 20pxbackground: #ffffffborder: 1px solid var(--app-field-border)- Hover: Blaue Highlight-Farbe
- Fokus: Blauer Ring
rgba(37,99,235,0.18)
10. Filter-Panels
HorizontalLayout filterBar = new HorizontalLayout();
filterBar.addClassName("filter-panel");
filterBar.setWidthFull();
.filter-panel {
padding: 1rem 1.15rem;
border-radius: 26px;
flex-wrap: wrap;
gap: 1rem;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
}
Wichtig: Filter-Felder niemals mit fixen Pixel-Breiten versehen → flex-wrap: wrap und min-width: 0 sorgen für korrektes Responsive-Verhalten.
11. Sidebar / Navigation
Nav-Rows
.app-nav-row {
border-radius: 18px;
border: 1px solid rgba(255,255,255,0.06);
background: rgba(255,255,255,0.08);
max-width: calc(100% - 4px);
transition: transform 0.18s ease, background-color 0.18s ease;
}
.app-nav-row:hover {
transform: translateX(4px);
background: rgba(255,255,255,0.14);
}
User Menu
.app-user-menu {
margin: auto 0.5rem 0.5rem;
padding: 0.45rem 0.65rem;
border-radius: 22px;
border: 1px solid rgba(255,255,255,0.1);
background: rgba(255,255,255,0.08);
backdrop-filter: blur(14px);
}
12. Karten-Hierarchie
| Klasse | Radius | Hintergrund | Einsatz |
|---|---|---|---|
surface-panel |
28px | rgba(255,255,255,0.78) + Blur | Hauptdatenpanels |
form-card |
28px | rgba(255,255,255,0.90) + Blur | Formular-Cards |
section-card, content-card |
26px | rgba(255,255,255,0.88) | Abschnittspanels |
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 |
message-card |
22px | Linear-Gradient weiß | Nachrichten |
station-tile |
24px | Linear-Gradient weiß | Station-Kacheln |
job-task-card |
22px | Linear-Gradient weiß | Aufgaben-Karten |
13. Station Tiles & Kacheln
Station Tile
.station-tile {
border-radius: 24px;
border: 1px solid var(--app-border-strong);
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,255,0.9));
box-shadow: var(--app-shadow-sm);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.station-tile:hover {
transform: translateY(-3px);
box-shadow: var(--app-shadow-md);
border-color: rgba(37,99,235,0.24);
}
.station-tile.validated {
background: linear-gradient(180deg, rgba(236,253,245,0.98), rgba(209,250,229,0.88));
border-color: rgba(5,150,105,0.26);
}
Add Station Tile (gestrichelt)
.add-station-tile {
background: linear-gradient(180deg, rgba(241,245,249,0.9), rgba(248,250,252,0.98));
border-style: dashed;
}
Stations Grid
.stations-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
width: 100%;
}
14. Job Task Cards
.job-task-card {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border: 1px solid var(--app-border-strong);
border-radius: 22px;
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.9));
box-shadow: var(--app-shadow-sm);
transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
}
.job-task-card:hover {
transform: translateY(-2px);
box-shadow: var(--app-shadow-md);
border-color: rgba(37,99,235,0.24);
}
.job-task-card.completed {
border-color: rgba(5,150,105,0.22);
background: linear-gradient(180deg, rgba(236,253,245,0.98), rgba(220,252,231,0.88));
}
Task Status Badges
.job-task-status.open {
background: rgba(220,38,38,0.12);
color: var(--lumo-error-text-color);
}
.job-task-status.completed {
background: rgba(5,150,105,0.12);
color: var(--lumo-success-text-color);
}
15. Dashboard Stat Cards
.dashboard-stat-card {
position: relative;
border-radius: 24px;
padding: 1.15rem;
border: 1px solid var(--app-border-strong);
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.92));
box-shadow: var(--app-shadow-md);
}
.dashboard-stat-card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
background: var(--dashboard-accent, var(--app-accent));
}
Akzent-Farben
.accent-blue { --dashboard-accent: #2563eb; }
.accent-green { --dashboard-accent: #059669; }
.accent-purple { --dashboard-accent: #7c3aed; }
.accent-orange { --dashboard-accent: #d97706; }
.accent-gray { --dashboard-accent: #64748b; }
.accent-red { --dashboard-accent: #dc2626; }
16. Message & Chat Components
Message Card
.message-card {
padding: 1rem;
border: 1px solid var(--app-border-strong);
border-radius: 22px;
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.9));
box-shadow: var(--app-shadow-sm);
transition: transform 0.22s ease, box-shadow 0.22s ease;
}
.message-card:hover {
transform: translateY(-4px);
box-shadow: 0 24px 48px rgba(15,23,42,0.14);
}
Chat Bubbles
.message-bubble {
max-width: min(78%, 720px);
padding: 0.85rem 1rem;
border-radius: 22px;
box-shadow: var(--app-shadow-sm);
}
.message-bubble.client {
background: rgba(255,255,255,0.96);
border: 1px solid var(--app-border-strong);
}
.message-bubble.server {
background: linear-gradient(135deg, rgba(191,219,254,0.8), rgba(167,243,208,0.74));
border: 1px solid rgba(59,130,246,0.22);
}
Chat AI/User Bubbles
.chat-bubble--user {
background: linear-gradient(135deg, #2563eb, #1d4ed8);
color: #eff6ff;
padding: 0.85rem 1rem;
border-radius: 24px;
}
.chat-bubble--ai {
background: rgba(255,255,255,0.94);
border: 1px solid var(--app-border-strong);
padding: 1rem;
border-radius: 24px;
}
17. Dialoge
vaadin-dialog-overlay::part(content) {
border-radius: 28px;
border: 1px solid var(--app-border-strong);
background: rgba(255,255,255,0.94);
backdrop-filter: blur(20px);
box-shadow: var(--app-shadow-xl);
}
Dialog Panels
| Klasse | Verwendung |
|---|---|
.dialog-form-panel |
Formulare im Dialog |
.dialog-content-panel |
Inhalte im Dialog |
.dialog-task-card |
Aufgaben-Karten im Dialog |
.dialog-cargo-card |
Fracht-Karten im Dialog |
.dialog-form-panel,
.dialog-content-panel {
backdrop-filter: blur(12px);
padding: 1rem;
border-radius: 24px;
}
18. Detail Cards
.detail-card {
border: 1px solid var(--app-border-strong);
border-radius: 24px;
background: rgba(255,255,255,0.9);
box-shadow: var(--app-shadow-sm);
padding: 1rem;
}
.detail-card--accent {
border-color: rgba(37,99,235,0.22);
background: linear-gradient(180deg, rgba(219,234,254,0.62), rgba(239,246,255,0.9));
}
.detail-card--code,
.detail-card--comment {
font-family: ui-monospace, "SFMono-Regular", monospace;
}
19. Route & Summary Cards
.route-card,
.summary-card {
border: 1px solid var(--app-border-strong);
border-radius: 24px;
background: rgba(255,255,255,0.84);
box-shadow: var(--app-shadow-sm);
padding: 1rem;
}
.services-panel,
.notes-panel {
border: 1px solid var(--app-border-strong);
border-radius: 24px;
background: rgba(255,255,255,0.84);
box-shadow: var(--app-shadow-sm);
padding: 0.75rem 1rem 1rem;
}
20. Login Seite
.login-shell {
width: min(1120px, 100%);
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(360px, 440px);
gap: 1.5rem;
}
.login-highlight {
position: relative;
overflow: hidden;
min-height: 560px;
padding: clamp(1.8rem, 4vw, 3rem);
border-radius: 32px;
border: 1px solid rgba(255,255,255,0.14);
background: linear-gradient(140deg, #081224 0%, #173d8d 52%, #0f766e 100%);
box-shadow: var(--app-shadow-xl);
}
.login-card {
width: 100%;
padding: clamp(1.35rem, 3vw, 2rem);
border-radius: 32px;
border: 1px solid var(--app-border-strong);
background: rgba(255,255,255,0.88);
backdrop-filter: blur(20px);
box-shadow: var(--app-shadow-lg);
}
Eyebrow Chip
.eyebrow-chip {
display: inline-flex;
align-items: center;
padding: 0.45rem 0.8rem;
border-radius: 999px;
background: rgba(255,255,255,0.14);
color: rgba(248,250,252,0.92);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
21. Landing Page
.landing-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.landing-logo {
font-size: clamp(1.6rem, 4vw, 2.2rem);
font-weight: 800;
letter-spacing: -0.05em;
}
.landing-nav-button,
.landing-language-button {
border-radius: 999px;
}
Hero Panel
.hero-panel {
position: relative;
overflow: hidden;
min-height: 340px;
justify-content: center;
text-align: center;
border-radius: 34px;
border: 1px solid rgba(255,255,255,0.14);
background: linear-gradient(135deg, #081224 0%, #1d4ed8 54%, #0f766e 100%);
box-shadow: var(--app-shadow-xl);
}
.hero-panel-title {
color: #f8fafc;
font-size: clamp(2rem, 5vw, 3.4rem);
font-weight: 800;
letter-spacing: -0.06em;
}
.hero-panel-text {
color: rgba(226,232,240,0.92);
font-size: clamp(1rem, 2vw, 1.15rem);
}
22. Timeline Components
.timeline-entry-card {
margin-bottom: 0.75rem;
padding: 1rem;
border: 1px solid var(--app-border-strong);
border-radius: 24px;
background: rgba(255,255,255,0.9);
box-shadow: var(--app-shadow-sm);
border-left: 4px solid var(--timeline-accent, var(--app-accent));
}
.timeline-reason {
font-weight: 700;
color: var(--app-text-strong);
}
.timeline-timestamp {
color: var(--app-text-muted);
font-size: 0.78rem;
}
23. Invoice Components
Invoice Generator
.invoice-generator-panel {
backdrop-filter: blur(16px);
border-radius: 24px;
padding: 1rem;
border: 1px solid var(--app-border-strong);
background: rgba(255,255,255,0.9);
}
.invoice-generator-template {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.95rem 1rem;
border: 1px solid var(--app-border-strong);
border-radius: 18px;
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.92));
cursor: grab;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.invoice-generator-template:hover {
transform: translateY(-2px);
box-shadow: var(--app-shadow-sm);
}
.invoice-generator-canvas {
position: relative;
overflow: hidden;
border: 2px dashed rgba(148,163,184,0.5);
border-radius: 24px;
background:
linear-gradient(180deg, rgba(255,255,255,0.95), rgba(241,245,249,0.9)),
linear-gradient(90deg, rgba(148,163,184,0.08) 1px, transparent 1px),
linear-gradient(rgba(148,163,184,0.08) 1px, transparent 1px);
background-size: auto, 28px 28px, 28px 28px;
}
Price Table
.price-table {
width: 100%;
}
.price-row {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.35rem 0;
}
.price-row--strong .price-row-label,
.price-row--strong .price-row-value {
font-weight: 800;
color: var(--app-text-strong);
}
24. Buttons
vaadin-button {
font-weight: 700;
border-radius: var(--lumo-border-radius-m);
transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease;
}
vaadin-button:not([disabled]):hover {
transform: translateY(-1px);
}
vaadin-button[theme~="primary"] {
box-shadow: 0 8px 20px rgba(37,99,235,0.22);
}
vaadin-button[theme~="primary"][theme~="success"] {
box-shadow: 0 8px 20px rgba(5,150,105,0.24);
}
vaadin-button[theme~="primary"][theme~="error"] {
box-shadow: 0 8px 20px rgba(220,38,38,0.24);
}
vaadin-button[disabled] {
opacity: 0.46;
transform: none !important;
}
Pill-Buttons (Nav, Landing): border-radius: 999px
25. Overflow & Box-Model Regeln
Pflicht bei allen Container-Elementen
component.getStyle()
.set("box-sizing", "border-box")
.set("max-width", "100%")
.set("min-width", "0");
Warum box-sizing: border-box kritisch ist
Ohne box-sizing: border-box gilt:
Gesamtbreite = width + padding-left + padding-right + border-left + border-right
surface-panelhatborder: 1px→ ohne box-sizing ist es100% + 2px→ Overflowdata-grid-panelhatpadding: 0.8rem→ ohne box-sizing ist es100% + 1.6rem→ Overflow
Warum min-width: 0 kritisch ist
In Flex-Containern verhindert die Default-Eigenschaft min-width: auto, dass Flex-Items kleiner werden als ihr Inhalt. min-width: 0 erlaubt das Schrumpfen unter die Inhaltsgröße.
26. Lumo-Override-Strategie
Padding-Override
/* Lumo: vaadin-vertical-layout[theme~="padding"] → Spezifizität 0,0,1,1 */
/* Override braucht mindestens 0,0,2,0: */
vaadin-vertical-layout.form-shell:not(.form-card) { padding: 0; }
Inline-Style-Override
setJustifyContentMode() setzt Inline-Styles → nur !important überschreibt:
vaadin-vertical-layout.data-view {
justify-content: flex-start !important;
align-items: stretch !important;
}
Regel: !important nur für Inline-Style-Overrides verwenden.
27. Responsive Verhalten
Breakpoints
| Breakpoint | Änderungen |
|---|---|
max-width: 980px |
Login-Shell wird einspaltig |
max-width: 720px |
view-container Margin reduziert auf 10px, border-radius auf 8px |
max-height: 820px |
Drawer-Header und Nav-Rows bekommen kompakteres Padding |
Mobile View-Container
@media (max-width: 720px) {
.view-container {
margin: 10px;
width: calc(100% - 20px);
height: calc(100dvh - 20px);
border-radius: 8px;
}
}
Flex-Wrap-Pflicht
Alle horizontalen Layouts, die Felder oder Buttons enthalten, müssen flex-wrap: wrap haben:
layout.getStyle().set("flex-wrap", "wrap");
28. Photo Gallery
.job-photo-gallery {
max-width: 600px;
min-height: 500px;
height: 500px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: rgba(255,255,255,0.96);
border-radius: 24px;
}
.job-photo-counter {
position: absolute;
top: var(--lumo-space-s);
right: var(--lumo-space-s);
padding: var(--lumo-space-xs) var(--lumo-space-s);
border-radius: 999px;
background: rgba(15,23,42,0.72);
color: #f8fafc;
font-weight: 700;
}
.job-photo-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
border-radius: 999px;
background: rgba(255,255,255,0.9);
box-shadow: var(--app-shadow-sm);
}
29. Empty State
.empty-state-card {
text-align: center;
color: var(--app-text-muted);
padding: 1rem;
border: 1px dashed var(--app-border-strong);
border-radius: 24px;
background: rgba(248,250,252,0.74);
}
30. Inline Flash (Fehlermeldungen)
.inline-flash {
width: 100%;
padding: 0.95rem 1rem;
border-radius: 18px;
border: 1px solid rgba(220,38,38,0.22);
background: rgba(254,242,242,0.92);
color: var(--lumo-error-text-color);
box-sizing: border-box;
}
31. Checkliste für neue Views
Beim Erstellen einer neuen View folgende Punkte prüfen:
- Root-Element hat genau eine View-Klasse (
data-view,form-page, etc.) - Kein
setPadding(true)auf dem View-Root oderform-shell-Wrappern (ohneform-card) - Kein
setJustifyContentMode(CENTER)auf Daten-Views (data-view,admin-form-view) - Alle Container haben
max-width: 100%undbox-sizing: border-box - Alle Flex-Container mit wechselnden Inhalten haben
flex-wrap: wrap - Formularfelder haben keine fixen Pixel-Breiten (
setWidth("200px")verboten) ViewToolbarist das erste Kind-Element- Grid-Panel verwendet
surface-panel data-grid-panelmitsetWidthFull() - Schmale Formular-Container haben
setWidth("Xpx")undsetMaxWidth("100%") - Inline-Styles nur für dynamische Werte verwenden, alles andere per CSS-Klassen