fix(redis): keep key browser actions usable in narrow panels

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-09-17 19:14:57 +08:00
1 parent be251d596e
commit df8a659cc1
2 files changed
+93 -28

No files matched your search

@@ -3215,7 +3215,9 @@ defineExpose({ focusSearch, insertCommand, executeCommand: executeAiCommand });
>{{ customGrouping.enabled ? t("redisGrouping.selectLoaded") : t("redis.selectAllLoaded") }}</Button
>
<Button v-if="checkedKeys.size > 0" variant="ghost" size="sm" class="h-6 shrink-0 px-1.5 text-xs" :disabled="selectionBusy" data-redis-deselect-all @click="clearAllCheckedKeys">{{ t("redis.deselectAll") }}</Button>
<Button v-if="checkedKeys.size > 0" variant="ghost" size="sm" class="h-6 shrink-0 px-1.5 text-xs" :disabled="selectionBusy" :title="t('redis.batchExpiry')" data-redis-batch-expiry @click="openBatchExpiryDialog"><Clock class="w-3 h-3 mr-1" />{{ t("redis.batchExpiry") }}</Button>
<Button v-if="checkedKeys.size > 0" variant="ghost" size="sm" class="h-6 shrink-0 px-1.5 text-xs" :disabled="selectionBusy" :title="t('redis.batchExpiry')" :aria-label="t('redis.batchExpiry')" data-redis-batch-expiry @click="openBatchExpiryDialog"
><Clock class="redis-expiry-icon w-3 h-3 mr-1" /><span class="redis-expiry-label">{{ t("redis.batchExpiry") }}</span></Button
>
<Button v-if="checkedKeys.size > 0" variant="ghost" size="sm" class="h-6 shrink-0 text-xs text-destructive" :disabled="selectionBusy" data-redis-batch-delete @click="requestBatchDelete"><Trash2 class="w-3 h-3 mr-1" />{{ checkedKeys.size }}</Button>
<Button variant="ghost" size="icon" class="h-6 w-6 shrink-0" :disabled="mutatingKeys || loading || loadingMore || isFetchingAll" @click="loadKeys">
<Loader2 v-if="loading" class="h-3 w-3 animate-spin" />
@@ -3828,14 +3830,17 @@ defineExpose({ focusSearch, insertCommand, executeCommand: executeAiCommand });
gap: 0.375rem;
}
.redis-search-mode-group,
.redis-key-toolbar-actions {
.redis-search-mode-group {
flex-wrap: nowrap;
min-width: 0;
justify-self: start;
}
.redis-search-mode-group {
justify-self: start;
/* Wrapping can never overlap; with nowrap + justify-end an overflowing row
spills left and paints over the search-mode segmented control (#9356). */
.redis-key-toolbar-actions {
flex-wrap: wrap;
min-width: 0;
}
.redis-search-mode-button {
@@ -3856,39 +3861,43 @@ defineExpose({ focusSearch, insertCommand, executeCommand: executeAiCommand });
gap: 0.375rem;
}
@container (max-width: 320px) {
/* Toolbar max-content is ~730px; collapse the widest label to icon-only before
the row reaches the overflow band (covers ~320px..740px, see #9356). */
@container (max-width: 740px) {
.redis-expiry-label {
display: none;
}
.redis-expiry-icon {
margin-right: 0;
}
}
@container (max-width: 340px) {
/* Small-window fallback below the 360px pane floor: two rows —
[mode | count] and the action buttons (#9356). */
.redis-key-toolbar-header {
grid-template-columns: auto auto minmax(0, 1fr);
grid-template-columns: auto minmax(0, 1fr);
}
.redis-search-mode-group {
grid-column: 1;
grid-row: 1;
}
.redis-key-count {
grid-column: 1 / -1;
grid-row: 2;
text-align: left;
grid-column: 2;
grid-row: 1;
}
.redis-key-toolbar-actions {
grid-column: 2;
grid-row: 1;
grid-column: 1 / -1;
grid-row: 2;
justify-content: flex-start;
}
}
@container (max-width: 240px) {
.redis-search-mode-group {
grid-column: 1 / -1;
grid-row: 1;
}
.redis-key-count {
grid-column: 1;
grid-row: 2;
}
.redis-key-toolbar-actions {
grid-column: 2;
grid-row: 2;
}
.redis-fuzzy-label {
display: none;
}
@@ -3908,7 +3917,7 @@ defineExpose({ focusSearch, insertCommand, executeCommand: executeAiCommand });
}
.redis-workspace-splitpanes > :deep(.splitpanes__pane:first-child) {
min-width: min(256px, 64%);
min-width: min(360px, 64%);
}
.redis-workspace-splitpanes :deep(.splitpanes--vertical > .splitpanes__splitter) {
@@ -0,0 +1,56 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// jsdom cannot lay out flex/grid or evaluate container queries, so the #9356
// overlap fix is guarded at the source level: the toolbar actions group must
// wrap (a nowrap + justify-end row spills LEFT onto the search-mode segmented
// control when the key pane is ~320px..730px wide), and the batch-expiry label
// must collapse to an icon before the row reaches that overflow band.
const source = readFileSync(new URL("../RedisKeyBrowser.vue", import.meta.url), "utf8");
function styleRule(selector: string): string {
// Anchor on a line-start selector so template `class="..."` occurrences never match.
const start = source.indexOf(`\n${selector} {`);
expect(start).toBeGreaterThan(-1);
const open = source.indexOf("{", start);
const close = source.indexOf("}", open);
return source.slice(open + 1, close);
}
describe("redis key browser toolbar layout (#9356)", () => {
it("lets the toolbar actions wrap instead of overflowing onto the search-mode group", () => {
expect(styleRule(".redis-key-toolbar-actions")).toContain("flex-wrap: wrap");
});
it("collapses the batch-expiry label to an icon in a 740px container tier", () => {
const tierStart = source.indexOf("@container (max-width: 740px)");
const tierEnd = source.indexOf("@container (max-width: 340px)");
expect(tierStart).toBeGreaterThan(-1);
expect(tierEnd).toBeGreaterThan(tierStart);
const tierBody = source.slice(tierStart, tierEnd);
expect(tierBody).toContain(".redis-expiry-label");
expect(tierBody).toContain(".redis-expiry-icon");
});
it("switches to two toolbar rows in the 340px container tier", () => {
const tierStart = source.indexOf("@container (max-width: 340px)");
const tierEnd = source.indexOf("@container (max-width: 240px)");
expect(tierStart).toBeGreaterThan(-1);
expect(tierEnd).toBeGreaterThan(tierStart);
const tierBody = source.slice(tierStart, tierEnd);
expect(tierBody).toContain("grid-template-columns: auto minmax(0, 1fr)");
const actionsRule = tierBody.slice(tierBody.indexOf(".redis-key-toolbar-actions"));
expect(actionsRule).toContain("grid-row: 2");
expect(actionsRule).toContain("justify-content: flex-start");
});
it("marks the batch-expiry icon and label in the template", () => {
expect(source).toMatch(/class="[^"]*redis-expiry-icon[^"]*"/);
expect(source).toMatch(/<span class="redis-expiry-label">/);
expect(source).toContain(`:aria-label="t('redis.batchExpiry')"`);
});
it("keeps the key pane at a 360px minimum width", () => {
expect(source).toContain("min-width: min(360px, 64%)");
});
});