fix: compact widget empty states, view transitions for reordering, widget body padding

- widget__empty: column → row layout, icon 28→20px, padding space-5 → space-3
  saves ~40px vertical space per empty widget on mobile, keeps populated widgets
  visible above the fold
- widget__body: bottom padding space-3 → space-4 for slightly more breathing room
- rebuildList() now uses document.startViewTransition with prefers-reduced-motion
  guard; each customize-row gets a stable view-transition-name for smooth reorder
  animation without a JS animation library

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-04-26 09:03:45 +02:00
parent 777e617b74
commit 3bfda59fc0
3 changed files with 23 additions and 15 deletions
+2 -2
View File
@@ -47,5 +47,5 @@
2. **Lack of Visual Feedback in Customization**: Reordering widgets in the customize modal (`rebuildList()`) happens instantly without transition, feeling jarring. 2. **Lack of Visual Feedback in Customization**: Reordering widgets in the customize modal (`rebuildList()`) happens instantly without transition, feeling jarring.
### Implementation Steps ### Implementation Steps
- [ ] **Compact Empty States (`dashboard.js`)**: Offen — `.widget__empty` hat bereits reduziertes Padding (`space-5`), aber kein echtes Row-Layout. Niedrige Priorität. - [x] **Compact Empty States (`dashboard.css`)**: `.widget__empty` auf horizontales Row-Layout umgestellt, Icon 28→20px, Padding reduziert — spart ~40px vertikalen Platz pro leerem Widget.
- [ ] **Animate Widget Reordering (`dashboard.js`)**: Offen — View Transition API wäre sinnvoll, aber kein Bug. Niedrige Priorität. - [x] **Animate Widget Reordering (`dashboard.js`)**: `rebuildList()` nutzt nun `document.startViewTransition()` mit `prefers-reduced-motion`-Guard und `view-transition-name` je Row.
+13 -5
View File
@@ -546,7 +546,7 @@ function openCustomizeModal(currentConfig, onSave) {
const isFirst = i === 0; const isFirst = i === 0;
const isLast = i === draft.length - 1; const isLast = i === draft.length - 1;
return ` return `
<div class="customize-row" data-id="${w.id}"> <div class="customize-row" data-id="${esc(w.id)}" style="view-transition-name: widget-row-${esc(w.id)}">
<label class="customize-row__toggle"> <label class="customize-row__toggle">
<input type="checkbox" class="customize-row__check" data-id="${w.id}" <input type="checkbox" class="customize-row__check" data-id="${w.id}"
${w.visible ? 'checked' : ''} aria-label="${widgetLabel(w.id)}"> ${w.visible ? 'checked' : ''} aria-label="${widgetLabel(w.id)}">
@@ -584,10 +584,18 @@ function openCustomizeModal(currentConfig, onSave) {
function rebuildList() { function rebuildList() {
const list = panel.querySelector('#customize-list'); const list = panel.querySelector('#customize-list');
if (!list) return; if (!list) return;
list.replaceChildren(); const doRebuild = () => {
list.insertAdjacentHTML('beforeend', buildRows()); list.replaceChildren();
if (window.lucide) window.lucide.createIcons({ el: list }); list.insertAdjacentHTML('beforeend', buildRows());
wireRows(); if (window.lucide) window.lucide.createIcons({ el: list });
wireRows();
};
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (document.startViewTransition && !reducedMotion) {
document.startViewTransition(doRebuild);
} else {
doRebuild();
}
} }
function wireRows() { function wireRows() {
+8 -8
View File
@@ -267,25 +267,25 @@
.widget__body { .widget__body {
flex: 1; flex: 1;
padding: var(--space-2) var(--space-4) var(--space-3); padding: var(--space-2) var(--space-4) var(--space-4);
} }
.widget__empty { .widget__empty {
padding: var(--space-5) var(--space-4); padding: var(--space-3) var(--space-4);
text-align: center;
color: var(--color-text-tertiary); color: var(--color-text-tertiary);
font-size: var(--text-sm); font-size: var(--text-sm);
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center; align-items: center;
gap: var(--space-1); gap: var(--space-2);
} }
.widget__empty .empty-state__icon { .widget__empty .empty-state__icon {
width: 28px; width: 20px;
height: 28px; height: 20px;
flex-shrink: 0;
color: var(--color-text-tertiary); color: var(--color-text-tertiary);
margin-bottom: var(--space-1); opacity: 0.6;
} }
/* Widget hover lift (desktop) */ /* Widget hover lift (desktop) */