feat: collapsible domain groups on focus page
Domain headers are now clickable to expand/collapse their item rows, with a chevron indicator and item count badge. State persists via localStorage, matching the sidebar toggle pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,7 +71,7 @@ async def focus_view(
|
|||||||
dl = item.get("domain_name") or "General"
|
dl = item.get("domain_name") or "General"
|
||||||
dc = item.get("domain_color") or ""
|
dc = item.get("domain_color") or ""
|
||||||
if dk not in domain_map:
|
if dk not in domain_map:
|
||||||
domain_map[dk] = {"label": dl, "color": dc, "rows": []}
|
domain_map[dk] = {"key": dk, "label": dl, "color": dc, "rows": []}
|
||||||
domain_map[dk]["rows"].append(item)
|
domain_map[dk]["rows"].append(item)
|
||||||
|
|
||||||
hierarchy = list(domain_map.values())
|
hierarchy = list(domain_map.values())
|
||||||
|
|||||||
@@ -1107,6 +1107,9 @@ a:hover { color: var(--accent-hover); }
|
|||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Focus Domain Toggle ---- */
|
||||||
|
.focus-domain-header:hover td { background: var(--surface3); }
|
||||||
|
|
||||||
/* ---- Focus Drag-and-Drop ---- */
|
/* ---- Focus Drag-and-Drop ---- */
|
||||||
.focus-drag-row { cursor: grab; }
|
.focus-drag-row { cursor: grab; }
|
||||||
.focus-drag-row.dragging { opacity: 0.4; background: var(--accent-soft); }
|
.focus-drag-row.dragging { opacity: 0.4; background: var(--accent-soft); }
|
||||||
|
|||||||
@@ -22,14 +22,18 @@
|
|||||||
</colgroup>
|
</colgroup>
|
||||||
{% for domain in hierarchy %}
|
{% for domain in hierarchy %}
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td colspan="9" style="padding:2px 8px;border-bottom:1px solid var(--border);background:var(--surface2);">
|
<tr class="focus-domain-header" data-domain-key="{{ domain.key }}" style="cursor:pointer;">
|
||||||
|
<td colspan="9" style="padding:2px 8px;border-bottom:1px solid var(--border);background:var(--surface2);">
|
||||||
<span style="display:inline-flex;align-items:center;gap:6px;">
|
<span style="display:inline-flex;align-items:center;gap:6px;">
|
||||||
|
<svg class="focus-domain-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" style="transition:transform 0.15s;flex-shrink:0;color:var(--muted);"><polyline points="4 2 8 6 4 10"/></svg>
|
||||||
<span style="width:8px;height:8px;border-radius:50%;background:{{ domain.color or 'var(--accent)' }};flex-shrink:0;"></span>
|
<span style="width:8px;height:8px;border-radius:50%;background:{{ domain.color or 'var(--accent)' }};flex-shrink:0;"></span>
|
||||||
<span style="font-weight:700;font-size:0.8rem;letter-spacing:0.03em;text-transform:uppercase;color:var(--text)">{{ domain.label }}</span>
|
<span style="font-weight:700;font-size:0.8rem;letter-spacing:0.03em;text-transform:uppercase;color:var(--text)">{{ domain.label }}</span>
|
||||||
|
<span style="font-size:0.72rem;color:var(--muted);font-weight:400;">{{ domain.rows|length }}</span>
|
||||||
</span>
|
</span>
|
||||||
</td></tr>
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tbody class="focus-drag-group">
|
<tbody class="focus-drag-group" data-domain-key="{{ domain.key }}">
|
||||||
{% for item in domain.rows %}
|
{% for item in domain.rows %}
|
||||||
<tr class="focus-drag-row" draggable="true" data-id="{{ item.id }}" style="border-bottom:1px solid var(--border);{{ 'opacity:0.6;' if item.completed }}">
|
<tr class="focus-drag-row" draggable="true" data-id="{{ item.id }}" style="border-bottom:1px solid var(--border);{{ 'opacity:0.6;' if item.completed }}">
|
||||||
<td style="padding:1px 1px;vertical-align:middle;">{% with reorder_url="/focus/reorder", item_id=item.id, extra_fields={"focus_date": focus_date|string} %}{% include 'partials/reorder_arrows.html' %}{% endwith %}</td>
|
<td style="padding:1px 1px;vertical-align:middle;">{% with reorder_url="/focus/reorder", item_id=item.id, extra_fields={"focus_date": focus_date|string} %}{% include 'partials/reorder_arrows.html' %}{% endwith %}</td>
|
||||||
@@ -191,6 +195,26 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Domain group collapse/expand
|
||||||
|
document.querySelectorAll('.focus-domain-header').forEach(function(header) {
|
||||||
|
var key = 'focus-domain-' + header.dataset.domainKey;
|
||||||
|
var tbody = document.querySelector('tbody.focus-drag-group[data-domain-key="' + header.dataset.domainKey + '"]');
|
||||||
|
var chevron = header.querySelector('.focus-domain-chevron');
|
||||||
|
// Restore saved state
|
||||||
|
if (localStorage.getItem(key) === 'true') {
|
||||||
|
tbody.style.display = 'none';
|
||||||
|
chevron.style.transform = 'rotate(0deg)';
|
||||||
|
} else {
|
||||||
|
chevron.style.transform = 'rotate(90deg)';
|
||||||
|
}
|
||||||
|
header.addEventListener('click', function() {
|
||||||
|
var hidden = tbody.style.display === 'none';
|
||||||
|
tbody.style.display = hidden ? '' : 'none';
|
||||||
|
chevron.style.transform = hidden ? 'rotate(90deg)' : 'rotate(0deg)';
|
||||||
|
localStorage.setItem(key, !hidden);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Drag-and-drop reorder within domain groups
|
// Drag-and-drop reorder within domain groups
|
||||||
var dragRow = null;
|
var dragRow = null;
|
||||||
var dragGroup = null;
|
var dragGroup = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user