bucket-bar: hide scrollbar, soft edge fades, wheel-to-scroll
The horizontal scrollbar on the bucket row was ugly and inconsistent with the rest of the app's scrollbar-less panels. Hide it, redirect vertical wheel over the bar to horizontal scroll, and surface overflow with soft bg-coloured gradients on whichever side has more content off-screen: right only while anchored at the start, both in the middle, left only at the end. Fade overlays live on #bucket-row (parent) as pseudo-elements with pointer-events:none, rather than mask-image on the bar itself — a mask would also clip super-bucket popovers that are intentionally rendered inside #bucket-bar for the collab MutationObserver. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
41272bd9ce
commit
f2a33a9164
4 files changed
+83
-2
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
define('APP_NAME', 'hyprcrd');
|
||||
define('APP_VERSION', '1.9.6');
|
||||
define('APP_VERSION', '1.9.7');
|
||||
|
||||
// Database path — writable directory, outside webroot
|
||||
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hyprcrd",
|
||||
"version": "1.9.6",
|
||||
"version": "1.9.7",
|
||||
"description": "hyprcrd — self-hosted card manager",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -780,6 +780,50 @@ body.detail-image-fs-open { overflow: hidden; }
|
||||
background: color-mix(in srgb, var(--t-accent) 12%, transparent) !important;
|
||||
}
|
||||
|
||||
/* Bucket bar: no visible scrollbar, soft edge fades when the row
|
||||
overflows so the user knows there's more off-screen. Fade is
|
||||
implemented via absolutely-positioned overlay pseudo-elements on
|
||||
#bucket-row (parent) rather than mask-image on the bar itself,
|
||||
because mask-image would also clip super-bucket popovers rendered
|
||||
inside the bar. Overlays use pointer-events:none so clicks pass
|
||||
through to the tabs underneath. */
|
||||
#bucket-bar {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
#bucket-bar::-webkit-scrollbar { display: none; }
|
||||
|
||||
#bucket-row::before,
|
||||
#bucket-row::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 32px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
z-index: 2;
|
||||
}
|
||||
#bucket-row::before {
|
||||
left: 24px; /* matches .px-6 = 24px on #bucket-row */
|
||||
background: linear-gradient(to right, var(--t-bg) 0%, transparent 100%);
|
||||
}
|
||||
#bucket-row::after {
|
||||
/* Stop at the right edge of #bucket-bar (before the toolbar
|
||||
toggles). The toolbar is flex-0, bar is flex-1 — the easiest
|
||||
way is to inset the overlay by the total width of the toolbar
|
||||
via a right offset that the JS writes. Defaults to 24px (the
|
||||
.px-6 pad) and the JS overrides with the real value. */
|
||||
right: var(--bucket-row-right-inset, 24px);
|
||||
background: linear-gradient(to left, var(--t-bg) 0%, transparent 100%);
|
||||
}
|
||||
#bucket-row.fade-left::before,
|
||||
#bucket-row.fade-both::before { opacity: 1; }
|
||||
#bucket-row.fade-right::after,
|
||||
#bucket-row.fade-both::after { opacity: 1; }
|
||||
|
||||
/* Unread dot on the fixed `all` bucket-tab — appears when new cards
|
||||
arrive while the user is viewing a different bucket. Cleared the
|
||||
moment the user navigates back to all (renderBoard drains the
|
||||
|
||||
@@ -186,6 +186,43 @@ const inpBucket = $('inp-bucket');
|
||||
const searchFilter = $('search-bucket-filter');
|
||||
const tasksFilter = $('tasks-bucket-filter');
|
||||
|
||||
// ── Overflow handling for the bucket bar ─────────────────────────
|
||||
// Scrollbar is hidden (CSS). Mouse-wheel over the bar translates to
|
||||
// horizontal scroll so long bucket rows stay usable without a bar.
|
||||
// Edge fades kick in dynamically based on scroll position so the
|
||||
// user sees there's more off-screen — both sides at once in the
|
||||
// middle, one side at either extreme. Fade overlays live on the
|
||||
// parent #bucket-row (see CSS) so they don't clip super-bucket
|
||||
// popovers that render inside #bucket-bar itself.
|
||||
const barRow = $('bucket-row');
|
||||
function updateBucketBarFades() {
|
||||
if (!bar || !barRow) return;
|
||||
const canLeft = bar.scrollLeft > 2;
|
||||
const canRight = bar.scrollLeft + bar.clientWidth < bar.scrollWidth - 2;
|
||||
barRow.classList.toggle('fade-left', canLeft && !canRight);
|
||||
barRow.classList.toggle('fade-right', !canLeft && canRight);
|
||||
barRow.classList.toggle('fade-both', canLeft && canRight);
|
||||
// Right-side fade overlay is anchored to the parent's right edge;
|
||||
// the actual bar ends before the toolbar toggles. Compute the
|
||||
// distance so the gradient lines up with the bar's right edge.
|
||||
const rowRect = barRow.getBoundingClientRect();
|
||||
const barRect = bar.getBoundingClientRect();
|
||||
const rightInset = Math.max(0, rowRect.right - barRect.right);
|
||||
barRow.style.setProperty('--bucket-row-right-inset', rightInset + 'px');
|
||||
}
|
||||
if (bar) {
|
||||
bar.addEventListener('wheel', (e) => {
|
||||
if (e.deltaY === 0) return;
|
||||
if (bar.scrollWidth <= bar.clientWidth) return;
|
||||
e.preventDefault();
|
||||
bar.scrollLeft += e.deltaY;
|
||||
}, { passive: false });
|
||||
bar.addEventListener('scroll', updateBucketBarFades, { passive: true });
|
||||
window.addEventListener('resize', updateBucketBarFades);
|
||||
new MutationObserver(updateBucketBarFades).observe(bar, { childList: true, subtree: false });
|
||||
updateBucketBarFades();
|
||||
}
|
||||
|
||||
/** Switch the active bucket without going through the click path —
|
||||
* used by the number-key hotkeys (and any other code that wants to
|
||||
* activate a bucket programmatically without triggering the super-
|
||||
|
||||
Reference in new issue
Block a user