feat: unify sidebar panel header layout with SidebarTopArea component (#9740)

## Summary

Unify the search bar + action buttons layout across all left sidebar
panels (Node Library, Workflows, Model Library, Media Assets) using a
shared `SidebarTopArea` presentation component.

## Changes

- **What**:
- Add `SidebarTopArea.vue` — layout component with `flex-1` default slot
(search) and `#actions` slot (buttons), plus optional `bottomDivider`
prop
- Replace raw `<button>` elements in Node Library with `<Button
variant="secondary" size="icon">`
- Replace reka-ui `TabsTrigger` with shared `Tab/TabList` component in
Node Library
- Move Media Assets tab list from hover-only `#tool-buttons` to
always-visible header below search area
- Unify spacing (`gap-2`, `p-2 2xl:px-4`) and divider styles across all
sidebar panels
- Remove unused `assetType` prop and header from
`AssetsSidebarGridView`/`AssetsSidebarListView`

## Review Focus

- `SidebarTopArea` API simplicity — just slots + one optional prop
- Node Library still requires `TabsRoot` in the body for reka-ui
`TabsContent` in child panels
- Media Assets tabs are now always visible instead of hover-only

[screen-capture
(1).webm](https://github.com/user-attachments/assets/fe1d8f7b-5674-4bb3-9842-569e4c3af6c9)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9740-feat-unify-sidebar-panel-header-layout-with-SidebarTopArea-component-3206d73d365081ea8ba7fd6ac54e0169)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Jin Yi
2026-03-13 22:32:18 +09:00
committed by GitHub
parent 5167a511ce
commit 10b0350d01
19 changed files with 226 additions and 218 deletions

View File

@@ -1,5 +1,5 @@
<template>
<TabsContent value="all" class="h-full flex-1 overflow-y-auto">
<div class="h-full flex-1 overflow-y-auto">
<!-- Favorites section -->
<h3
class="mb-0 px-4 py-2 text-xs font-medium tracking-wide text-muted-foreground uppercase"
@@ -34,11 +34,10 @@
@add-to-favorites="handleAddToFavorites"
/>
</div>
</TabsContent>
</div>
</template>
<script setup lang="ts">
import { TabsContent } from 'reka-ui'
import { computed } from 'vue'
import TreeExplorerV2 from '@/components/common/TreeExplorerV2.vue'

View File

@@ -1,5 +1,5 @@
<template>
<TabsContent value="blueprints" class="h-full flex-1 overflow-y-auto">
<div class="h-full flex-1 overflow-y-auto">
<div v-for="(section, index) in sections" :key="section.title ?? index">
<h3
v-if="section.title"
@@ -14,12 +14,10 @@
@node-click="(node) => emit('nodeClick', node)"
/>
</div>
</TabsContent>
</div>
</template>
<script setup lang="ts">
import { TabsContent } from 'reka-ui'
import TreeExplorerV2 from '@/components/common/TreeExplorerV2.vue'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import type {

View File

@@ -1,5 +1,5 @@
<template>
<TabsContent value="custom" class="flex h-full flex-1 flex-col">
<div class="flex h-full flex-1 flex-col">
<div
v-for="(section, index) in sections"
:key="section.title ?? index"
@@ -30,12 +30,10 @@
{{ $t('g.manageExtensions') }}
</Button>
</div>
</TabsContent>
</div>
</template>
<script setup lang="ts">
import { TabsContent } from 'reka-ui'
import TreeExplorerV2 from '@/components/common/TreeExplorerV2.vue'
import Button from '@/components/ui/button/Button.vue'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'

View File

@@ -1,9 +1,5 @@
<template>
<TabsContent
ref="panelEl"
value="essentials"
class="h-full flex-1 overflow-y-auto px-3"
>
<div ref="panelEl" class="h-full flex-1 overflow-y-auto px-3">
<div class="flex flex-col gap-2 pb-6">
<!-- Flat sorted grid when alphabetical -->
<div
@@ -57,29 +53,26 @@
</CollapsibleRoot>
</template>
</div>
</TabsContent>
</div>
</template>
<script setup lang="ts">
import {
CollapsibleContent,
CollapsibleRoot,
CollapsibleTrigger,
TabsContent
CollapsibleTrigger
} from 'reka-ui'
import type { ComponentPublicInstance } from 'vue'
import { computed, provide, ref, watch } from 'vue'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
import { cn } from '@/utils/tailwindUtil'
const panelEl = ref<ComponentPublicInstance | null>(null)
const panelRef = computed(() => panelEl.value?.$el as HTMLElement | null)
provide('essentialsPanelRef', panelRef)
import EssentialNodeCard from './EssentialNodeCard.vue'
const panelEl = ref<HTMLDivElement | null>(null)
provide('essentialsPanelRef', panelEl)
const { root, flatNodes = [] } = defineProps<{
root: RenderedTreeExplorerNode<ComfyNodeDefImpl>
flatNodes?: RenderedTreeExplorerNode<ComfyNodeDefImpl>[]