feat: add persistent selected node highlight in sidebar tree

- Track selectedTreeNodeId in connection store
- Highlight any clicked tree node with focused (blue) / unfocused (gray) styles
- Use CSS :focus-within for automatic focus state switching
This commit is contained in:
t8y2
2026-05-14 21:52:06 +08:00
parent bf99e2609a
commit b6c88d7b65
6 changed files with 34 additions and 5 deletions
+2 -2
View File
@@ -273,7 +273,7 @@ defineExpose({ focusSearch });
</div>
<RecycleScroller
v-if="flatNodes.length > 0 && useVirtualTree"
class="connection-tree-scroller min-h-0 flex-1 overflow-y-auto overflow-x-auto"
class="sidebar-tree connection-tree-scroller min-h-0 flex-1 overflow-y-auto overflow-x-auto"
:items="flatNodes"
:item-size="SIDEBAR_TREE_ROW_HEIGHT"
:buffer="SIDEBAR_TREE_SCROLL_BUFFER"
@@ -294,7 +294,7 @@ defineExpose({ focusSearch });
/>
</template>
</RecycleScroller>
<div v-else-if="flatNodes.length > 0" class="min-h-0 flex-1 overflow-y-auto overflow-x-auto">
<div v-else-if="flatNodes.length > 0" class="sidebar-tree min-h-0 flex-1 overflow-y-auto overflow-x-auto">
<TreeItem
v-for="item in flatNodes"
:key="item.id"
+25 -3
View File
@@ -326,6 +326,7 @@ function runRowClickAction() {
}
function onClick(event: MouseEvent) {
connectionStore.selectedTreeNodeId = props.node.id;
if (event.detail > 1) return;
runRowClickAction();
}
@@ -1181,6 +1182,7 @@ const connectionColor = computed(() => {
const isActiveConnectionScope = computed(
() => !!props.node.connectionId && connectionStore.activeConnectionId === props.node.connectionId,
);
const isSelected = computed(() => connectionStore.selectedTreeNodeId === props.node.id);
const rowStyle = computed(() => {
const color = connectionColor.value;
return {
@@ -1374,14 +1376,16 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
<ContextMenuTrigger as-child>
<div>
<div
class="group flex min-w-0 items-center gap-1.5 py-1 px-2 cursor-pointer hover:bg-accent transition-colors relative"
class="group flex min-w-0 items-center gap-1.5 py-1 px-2 cursor-pointer hover:bg-accent transition-colors relative outline-none"
style="contain: layout style paint"
:class="{
'ring-1 ring-primary/50 bg-primary/5': showDropInside,
'opacity-50': isDragging,
'rounded-none': connectionColor,
'rounded-sm': !connectionColor,
'rounded-none': connectionColor && !isSelected,
'rounded-sm': !connectionColor && !isSelected,
'tree-item-active rounded-md': isSelected,
}"
:tabindex="isSelected ? 0 : -1"
:style="rowStyle"
@click="onClick"
@dblclick="onDoubleClick"
@@ -1966,3 +1970,21 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
@confirm="confirmDropSchema"
/>
</template>
<style>
/* Unfocused: subtle gray */
.tree-item-active {
background-color: oklch(0.94 0 0) !important;
}
:root.dark .tree-item-active {
background-color: oklch(0.26 0 0) !important;
}
/* Focused: soft blue */
.sidebar-tree:focus-within .tree-item-active {
background-color: oklch(0.91 0.03 250) !important;
}
:root.dark .sidebar-tree:focus-within .tree-item-active {
background-color: oklch(0.35 0.06 250) !important;
}
</style>
+2
View File
@@ -47,6 +47,7 @@ export const useConnectionStore = defineStore("connection", () => {
const connections = ref<ConnectionConfig[]>([]);
const isDesktop = isTauriRuntime();
const activeConnectionId = ref<string | null>(!isDesktop ? localStorage.getItem("dbx-active-connection") : null);
const selectedTreeNodeId = ref<string | null>(null);
watch(activeConnectionId, (id) => {
if (isDesktop) return;
@@ -1436,6 +1437,7 @@ export const useConnectionStore = defineStore("connection", () => {
return {
connections,
activeConnectionId,
selectedTreeNodeId,
treeNodes,
refreshAllTree,
refreshSavedSqlTree,