super-buckets: split tab click (activate) from caret click (dropdown)
Clicking the super-bucket tab now activates the last-shown child directly. Only the caret arrow on the right opens the dropdown popover. Caret has its own hit area with accent hover highlight. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
04b52ecc19
commit
527305355c
5 files changed
+64
-16
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
define('APP_NAME', 'hyprcrd');
|
||||
define('APP_VERSION', '1.2.0');
|
||||
define('APP_VERSION', '1.3.0');
|
||||
|
||||
// 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.2.0",
|
||||
"version": "1.3.0",
|
||||
"description": "hyprcrd — self-hosted card manager",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -821,6 +821,24 @@ body.anim-slow .board-card.card-pulse-local {
|
||||
/* Super-bucket dropdown popover. Positioned via inline styles by
|
||||
openSuperDropdown() in buckets.js. Lives on document.body (not
|
||||
inside #bucket-bar) so it can escape the bar's overflow:auto. */
|
||||
.super-caret {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 2px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s, background 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.super-caret:hover {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, var(--t-accent) 25%, transparent);
|
||||
}
|
||||
.bucket-tab.active .super-caret:hover {
|
||||
background: rgba(0,0,0,0.25);
|
||||
}
|
||||
#bucket-super-popover {
|
||||
z-index: 220;
|
||||
background: var(--t-surface);
|
||||
|
||||
@@ -309,7 +309,7 @@ function renderBar() {
|
||||
const shown = tileFor(b);
|
||||
const c = shown.color || '#64748b';
|
||||
const groupActive = state.activeBucket == b.id || kids.some(k => k.id == state.activeBucket);
|
||||
return `<button class="${tab(groupActive)} bucket-super" data-bucket-super="${b.id}" data-super-shown="${shown.id}" draggable="true" data-bucket-drag="${b.id}" style="border-top:3px solid ${c};${groupActive ? `background:${c};color:var(--t-bg);border-color:${c}` : ''}"><i class="ph ph-stack text-sm"></i><span class="font-pixel text-[10px]">${esc(shown.name)}</span><i class="ph ph-caret-down text-[10px] ml-0.5 opacity-70"></i></button>`;
|
||||
return `<button class="${tab(groupActive)} bucket-super" data-bucket-super="${b.id}" data-super-shown="${shown.id}" draggable="true" data-bucket-drag="${b.id}" style="border-top:3px solid ${c};${groupActive ? `background:${c};color:var(--t-bg);border-color:${c}` : ''}"><i class="ph ph-stack text-sm"></i><span class="font-pixel text-[10px]">${esc(shown.name)}</span><span class="super-caret" data-super-caret="${b.id}"><i class="ph ph-caret-down text-[10px]"></i></span></button>`;
|
||||
}
|
||||
const c = b.color || '#64748b';
|
||||
const active = state.activeBucket == b.id;
|
||||
@@ -462,11 +462,21 @@ function renderBar() {
|
||||
const isUser = isSuperTile || !SPECIAL.includes(bid);
|
||||
|
||||
if (isSuperTile) {
|
||||
// Click opens the dropdown popover instead of activating the
|
||||
// parent directly. The dropdown lists parent + every child.
|
||||
t.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
openSuperDropdown(t, parseInt(bid, 10));
|
||||
// Caret arrow opens the dropdown; clicking the rest of the
|
||||
// tab activates the last-shown child (or parent).
|
||||
const caret = t.querySelector('[data-super-caret]');
|
||||
if (caret) {
|
||||
caret.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
openSuperDropdown(t, parseInt(bid, 10));
|
||||
});
|
||||
}
|
||||
t.addEventListener('click', () => {
|
||||
const shownId = t.dataset.superShown;
|
||||
setActiveBucket(shownId || bid);
|
||||
state.activeGroup = null;
|
||||
renderBar();
|
||||
window._renderBoard();
|
||||
});
|
||||
} else {
|
||||
t.addEventListener('click', () => { setActiveBucket(bid); state.activeGroup = null; renderBar(); window._renderBoard(); });
|
||||
|
||||
@@ -180,7 +180,7 @@ describe('Super-buckets: Bar rendering', () => {
|
||||
expect(superTile.textContent).toContain('__sb_active_child__');
|
||||
});
|
||||
|
||||
it('clicking the super-tile opens a popover with parent + child entries', async () => {
|
||||
it('clicking the caret opens a popover with parent + child entries', async () => {
|
||||
const parent = await makeBucket('__sb_pop_parent__');
|
||||
const child1 = await makeBucket('__sb_pop_child1__');
|
||||
const child2 = await makeBucket('__sb_pop_child2__');
|
||||
@@ -191,15 +191,15 @@ describe('Super-buckets: Bar rendering', () => {
|
||||
|
||||
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
|
||||
if (!superTile) return;
|
||||
click(superTile);
|
||||
const caret = superTile.querySelector('[data-super-caret]');
|
||||
expect(caret).toExist();
|
||||
click(caret);
|
||||
await sleep(100);
|
||||
|
||||
const popover = document.getElementById('bucket-super-popover');
|
||||
expect(popover).toExist();
|
||||
// Parent + 2 children = 3 entries
|
||||
const entries = popover.querySelectorAll('[data-bucket]');
|
||||
expect(entries.length).toBe(3);
|
||||
// All three names appear
|
||||
const text = popover.textContent;
|
||||
expect(text).toContain('__sb_pop_parent__');
|
||||
expect(text).toContain('__sb_pop_child1__');
|
||||
@@ -215,7 +215,8 @@ describe('Super-buckets: Bar rendering', () => {
|
||||
|
||||
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
|
||||
if (!superTile) return;
|
||||
click(superTile);
|
||||
const caret = superTile.querySelector('[data-super-caret]');
|
||||
click(caret);
|
||||
await sleep(100);
|
||||
|
||||
const childEntry = document.querySelector(`#bucket-super-popover [data-bucket="${child.id}"]`);
|
||||
@@ -226,7 +227,25 @@ describe('Super-buckets: Bar rendering', () => {
|
||||
expect(String(state.activeBucket)).toBe(String(child.id));
|
||||
});
|
||||
|
||||
it('clicking the super-tile twice toggles the popover closed', async () => {
|
||||
it('clicking the super-tile activates the shown bucket', async () => {
|
||||
const parent = await makeBucket('__sb_direct_parent__');
|
||||
const child = await makeBucket('__sb_direct_child__');
|
||||
await patchBucket(child.id, { parent_id: parent.id });
|
||||
await window._reloadAll?.();
|
||||
await sleep(200);
|
||||
|
||||
state.activeBucket = 'all';
|
||||
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
|
||||
if (!superTile) return;
|
||||
click(superTile);
|
||||
await sleep(150);
|
||||
|
||||
const shownId = superTile.dataset.superShown;
|
||||
expect(String(state.activeBucket)).toBe(String(shownId));
|
||||
expect(document.getElementById('bucket-super-popover')).toBe(null);
|
||||
});
|
||||
|
||||
it('clicking the caret twice toggles the popover closed', async () => {
|
||||
const parent = await makeBucket('__sb_toggle_parent__');
|
||||
const child = await makeBucket('__sb_toggle_child__');
|
||||
await patchBucket(child.id, { parent_id: parent.id });
|
||||
@@ -235,10 +254,11 @@ describe('Super-buckets: Bar rendering', () => {
|
||||
|
||||
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
|
||||
if (!superTile) return;
|
||||
click(superTile);
|
||||
const caret = superTile.querySelector('[data-super-caret]');
|
||||
click(caret);
|
||||
await sleep(100);
|
||||
expect(document.getElementById('bucket-super-popover')).toExist();
|
||||
click(superTile);
|
||||
click(caret);
|
||||
await sleep(100);
|
||||
expect(document.getElementById('bucket-super-popover')).toBe(null);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user