mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-24 12:54:48 -06:00
fix(ui): improve delete workstream UX and accessibility (#339)
* fix(ui): improve delete workstream UX and accessibility Card body click no longer deselects (prevents confusing red border loss); checkbox pulse hint guides users to deselect affordance. Adds keyboard navigation, aria-labels, hover feedback, animations, and neutral Close button styling after deletion. * fix(ui): remove duplicate a11y checkbox from delete-mode cards Hide the visual checkbox from the a11y tree and tab order so the card (role=checkbox) is the sole keyboard/screen-reader target. Addresses Copilot review feedback about nested interactive elements.
This commit is contained in:
committed by
Patrick Buckley
parent
b038dbdd5b
commit
e9c141aba5
@@ -3368,19 +3368,39 @@ function renderSavedWorkstreams(items) {
|
||||
chk.type = "checkbox";
|
||||
chk.className = "ws-card-check";
|
||||
chk.checked = !!_wsDeleteSelected[sess.ws_id];
|
||||
chk.setAttribute("tabindex", "-1");
|
||||
chk.setAttribute("aria-hidden", "true");
|
||||
chk.onclick = function (e) {
|
||||
e.stopPropagation();
|
||||
if (chk.checked) _wsDeleteSelected[sess.ws_id] = true;
|
||||
else delete _wsDeleteSelected[sess.ws_id];
|
||||
card.classList.toggle("ws-selected", chk.checked);
|
||||
card.setAttribute("aria-checked", chk.checked ? "true" : "false");
|
||||
updateWsDeleteBar();
|
||||
};
|
||||
card.appendChild(chk);
|
||||
card.setAttribute("tabindex", "0");
|
||||
card.setAttribute("role", "checkbox");
|
||||
card.setAttribute("aria-checked", chk.checked ? "true" : "false");
|
||||
card.onclick = function (e) {
|
||||
if (e.target === chk) return;
|
||||
chk.checked = !chk.checked;
|
||||
if (chk.checked) {
|
||||
chk.classList.add("ws-check-hint");
|
||||
setTimeout(function () {
|
||||
chk.classList.remove("ws-check-hint");
|
||||
}, 600);
|
||||
return;
|
||||
}
|
||||
chk.checked = true;
|
||||
chk.onclick(e);
|
||||
};
|
||||
card.onkeydown = function (e) {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
chk.checked = !chk.checked;
|
||||
chk.onclick(e);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
card.setAttribute("role", "button");
|
||||
card.setAttribute("tabindex", "0");
|
||||
@@ -3505,6 +3525,7 @@ function confirmWsDeleteSelection() {
|
||||
if (delBtn) {
|
||||
delBtn.textContent = "Delete";
|
||||
delBtn.disabled = false;
|
||||
delBtn.classList.remove("ws-delete-close");
|
||||
delBtn.onclick = confirmWsDelete;
|
||||
}
|
||||
var cancelBtn = document.getElementById("ws-delete-cancel-btn");
|
||||
@@ -3636,6 +3657,7 @@ function confirmWsDelete() {
|
||||
if (delBtn) {
|
||||
delBtn.disabled = false;
|
||||
delBtn.textContent = "Close";
|
||||
delBtn.classList.add("ws-delete-close");
|
||||
delBtn.onclick = function () {
|
||||
cancelWsDelete();
|
||||
cancelWsDeleteMode();
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div id="ws-delete-bar" class="ws-delete-bar">
|
||||
<span class="ws-delete-count-label" id="ws-delete-bar-count" role="status" aria-live="polite" aria-atomic="true">0 selected</span>
|
||||
<button class="ws-delete-cancel-btn" onclick="cancelWsDeleteMode()">Cancel</button>
|
||||
<button class="ws-delete-cancel-btn" id="ws-delete-bar-select-all" onclick="toggleSelectAll()">Select All</button>
|
||||
<button class="ws-delete-selectall-btn" id="ws-delete-bar-select-all" onclick="toggleSelectAll()">Select All</button>
|
||||
<button class="ws-delete-bar-btn" id="ws-delete-bar-delete" onclick="confirmWsDeleteSelection()" disabled>Delete Selected</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1504,8 +1504,12 @@ audio.media-player {
|
||||
.dashboard-card .card-meta { font-size: 11px; color: var(--fg-dim); }
|
||||
|
||||
/* Delete mode */
|
||||
.dashboard-card.ws-delete-mode { cursor: default; }
|
||||
.dashboard-card.ws-delete-mode:hover { border-color: var(--border); background: var(--bg-surface); }
|
||||
.dashboard-card.ws-delete-mode { cursor: pointer; }
|
||||
.dashboard-card.ws-delete-mode:hover { border-color: var(--red); background: rgba(248, 113, 113, 0.04); }
|
||||
.dashboard-card.ws-delete-mode.ws-selected { cursor: default; }
|
||||
.dashboard-card.ws-delete-mode.ws-selected:hover { border-color: var(--red); background: rgba(248, 113, 113, 0.08); }
|
||||
[data-theme="light"] .dashboard-card.ws-delete-mode:hover { background: rgba(220, 38, 38, 0.04); }
|
||||
[data-theme="light"] .dashboard-card.ws-delete-mode.ws-selected:hover { background: rgba(220, 38, 38, 0.08); }
|
||||
.ws-card-check {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
@@ -1515,7 +1519,16 @@ audio.media-player {
|
||||
accent-color: var(--red);
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
animation: ws-check-fadein 0.2s ease-out forwards;
|
||||
}
|
||||
.ws-card-check.ws-check-hint {
|
||||
outline: 2px solid var(--red);
|
||||
outline-offset: 2px;
|
||||
animation: ws-check-pulse 0.6s ease-out;
|
||||
}
|
||||
@keyframes ws-check-fadein { to { opacity: 1; } }
|
||||
@keyframes ws-check-pulse { 0% { transform: scale(1.3); } 100% { transform: scale(1); } }
|
||||
.dashboard-card.ws-selected {
|
||||
border-color: var(--red);
|
||||
background: rgba(248, 113, 113, 0.08);
|
||||
@@ -1533,7 +1546,13 @@ audio.media-player {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.ws-delete-bar.visible { display: flex; }
|
||||
.ws-delete-bar.visible { display: flex; animation: ws-bar-slide 0.2s ease-out; }
|
||||
@keyframes ws-bar-slide { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ws-card-check { animation: none; opacity: 1; }
|
||||
.ws-card-check.ws-check-hint { animation: none; }
|
||||
.ws-delete-bar.visible { animation: none; }
|
||||
}
|
||||
.ws-delete-bar .ws-delete-count-label { font-size: 12px; color: var(--fg-dim); }
|
||||
.ws-delete-bar .ws-delete-bar-btn {
|
||||
margin-left: auto;
|
||||
@@ -1563,6 +1582,20 @@ audio.media-player {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-highlight);
|
||||
}
|
||||
.ws-delete-bar .ws-delete-selectall-btn {
|
||||
background: transparent;
|
||||
color: var(--fg-bright);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ws-delete-bar .ws-delete-selectall-btn:hover {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-highlight);
|
||||
}
|
||||
|
||||
/* Delete modal */
|
||||
#ws-delete-overlay {
|
||||
@@ -1608,6 +1641,11 @@ audio.media-player {
|
||||
color: #fff;
|
||||
border-color: var(--red);
|
||||
}
|
||||
#ws-delete-buttons button.ws-delete-close {
|
||||
background: transparent;
|
||||
color: var(--fg-bright);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
/* Server dashboard row — clickable */
|
||||
.dash-row { cursor: pointer; }
|
||||
|
||||
Reference in New Issue
Block a user