Cleanup: Sidebar Tabs component and style alignment (#7215)

## Summary

Unify the current sidebar tabs, structurally and aesthetically.

## Changes

- Removes the Assets only Layout
- Standardizes the title styling and spacing across the tabs.

## Review Focus

<!-- Critical design decisions or edge cases that need attention -->

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7215-WIP-Sidebar-Tabs-component-and-style-alignment-2c26d73d3650817193bfd752e0d0bbde)
by [Unito](https://www.unito.io)
This commit is contained in:
Alexander Brown
2025-12-08 23:33:08 -08:00
committed by GitHub
parent 51a336fd36
commit 5b91434ac4
13 changed files with 99 additions and 125 deletions

View File

@@ -10,12 +10,12 @@
class="bg-transparent"
>
<div class="flex w-full justify-between">
<div class="tabs-container">
<div class="tabs-container font-inter">
<Tab
v-for="tab in bottomPanelStore.bottomPanelTabs"
:key="tab.id"
:value="tab.id"
class="m-1 mx-2 border-none"
class="m-1 mx-2 border-none font-inter"
:class="{
'tab-list-single-item':
bottomPanelStore.bottomPanelTabs.length === 1

View File

@@ -2,7 +2,7 @@
<Tree
v-model:expanded-keys="expandedKeys"
v-model:selection-keys="selectionKeys"
class="tree-explorer px-2 py-0 2xl:px-4"
class="tree-explorer px-2 py-0 2xl:px-4 bg-transparent"
:class="props.class"
:value="renderedRoot.children"
selection-mode="single"

View File

@@ -182,7 +182,7 @@ function handleTitleCancel() {
<Tab
v-for="tab in tabs"
:key="tab.value"
class="text-sm py-1 px-2"
class="text-sm py-1 px-2 font-inter"
:value="tab.value"
>
{{ tab.label() }}

View File

@@ -1,36 +0,0 @@
<template>
<div
class="flex h-full flex-col bg-interface-panel-surface"
:class="props.class"
>
<div>
<div
v-if="slots.top"
class="flex min-h-12 items-center border-b border-interface-stroke px-4 py-2"
>
<slot name="top" />
</div>
<div v-if="slots.header" class="px-4 pb-4">
<slot name="header" />
</div>
</div>
<!-- min-h-0 to force scrollpanel to grow -->
<ScrollPanel class="min-h-0 grow">
<slot name="body" />
</ScrollPanel>
<div v-if="slots.footer">
<slot name="footer" />
</div>
</div>
</template>
<script setup lang="ts">
import ScrollPanel from 'primevue/scrollpanel'
import { useSlots } from 'vue'
const props = defineProps<{
class?: string
}>()
const slots = useSlots()
</script>

View File

@@ -1,19 +1,21 @@
<template>
<AssetsSidebarTemplate>
<template #top>
<span v-if="!isInFolderView" class="font-bold">
{{ $t('sideToolbar.mediaAssets.title') }}
</span>
<div v-else class="flex w-full items-center justify-between gap-2">
<SidebarTabTemplate
:title="isInFolderView ? '' : $t('sideToolbar.mediaAssets.title')"
>
<template #alt-title>
<div
v-if="isInFolderView"
class="flex w-full items-center justify-between gap-2"
>
<div class="flex items-center gap-2">
<span class="font-bold">{{ $t('Job ID') }}:</span>
<span class="font-bold">{{ $t('assetBrowser.jobId') }}:</span>
<span class="text-sm">{{ folderPromptId?.substring(0, 8) }}</span>
<button
class="m-0 cursor-pointer border-0 bg-transparent p-0 outline-0"
role="button"
@click="copyJobId"
>
<i class="mb-1 icon-[lucide--copy] text-sm"></i>
<i class="icon-[lucide--copy] text-sm"></i>
</button>
</div>
<div>
@@ -23,7 +25,7 @@
</template>
<template #header>
<!-- Job Detail View Header -->
<div v-if="isInFolderView" class="pt-4 pb-2">
<div v-if="isInFolderView" class="px-2 2xl:px-4">
<IconTextButton
:label="$t('sideToolbar.backToAssets')"
type="secondary"
@@ -35,15 +37,20 @@
</IconTextButton>
</div>
<!-- Normal Tab View -->
<TabList v-else v-model="activeTab" class="pt-4 pb-1">
<Tab value="output">{{ $t('sideToolbar.labels.generated') }}</Tab>
<Tab value="input">{{ $t('sideToolbar.labels.imported') }}</Tab>
<TabList v-else v-model="activeTab" class="font-inter px-2 2xl:px-4">
<Tab class="font-inter" value="output">{{
$t('sideToolbar.labels.generated')
}}</Tab>
<Tab class="font-inter" value="input">{{
$t('sideToolbar.labels.imported')
}}</Tab>
</TabList>
<!-- Filter Bar -->
<MediaAssetFilterBar
v-model:search-query="searchQuery"
v-model:sort-by="sortBy"
v-model:media-type-filters="mediaTypeFilters"
class="pb-1 px-2 2xl:px-4"
:show-generation-time-sort="activeTab === 'output'"
/>
</template>
@@ -158,7 +165,7 @@
</div>
</div>
</template>
</AssetsSidebarTemplate>
</SidebarTabTemplate>
<ResultGallery
v-model:active-index="galleryActiveIndex"
:all-gallery-items="galleryItems"
@@ -177,6 +184,7 @@ import TextButton from '@/components/button/TextButton.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import VirtualGrid from '@/components/common/VirtualGrid.vue'
import Load3dViewerContent from '@/components/load3d/Load3dViewerContent.vue'
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
import Tab from '@/components/tab/Tab.vue'
import TabList from '@/components/tab/TabList.vue'
@@ -194,8 +202,6 @@ import { useDialogStore } from '@/stores/dialogStore'
import { ResultItemImpl } from '@/stores/queueStore'
import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'
import AssetsSidebarTemplate from './AssetSidebarTemplate.vue'
const activeTab = ref<'input' | 'output'>('output')
const folderPromptId = ref<string | null>(null)
const folderExecutionTime = ref<number | undefined>(undefined)

View File

@@ -1,8 +1,5 @@
<template>
<SidebarTabTemplate
:title="$t('sideToolbar.modelLibrary')"
class="bg-(--p-tree-background)"
>
<SidebarTabTemplate :title="$t('sideToolbar.modelLibrary')">
<template #tool-buttons>
<Button
v-tooltip.bottom="$t('g.refresh')"

View File

@@ -3,7 +3,6 @@
<SidebarTabTemplate
v-if="!isHelpOpen"
:title="$t('sideToolbar.nodeLibrary')"
class="bg-(--p-tree-background)"
>
<template #tool-buttons>
<Button

View File

@@ -3,12 +3,15 @@
class="comfy-vue-side-bar-container group/sidebar-tab flex h-full flex-col"
:class="props.class"
>
<div class="comfy-vue-side-bar-header">
<Toolbar class="min-h-8 rounded-none border-x-0 border-t-0 px-2 py-1">
<div class="comfy-vue-side-bar-header flex flex-col gap-2">
<Toolbar
class="min-h-15.5 bg-transparent rounded-none border-x-0 border-t-0 px-2 2xl:px-4"
>
<template #start>
<span class="truncate text-xs 2xl:text-sm" :title="props.title">
{{ props.title.toUpperCase() }}
<span class="truncate font-bold" :title="props.title">
{{ props.title }}
</span>
<slot name="alt-title" />
</template>
<template #end>
<div

View File

@@ -1,7 +1,7 @@
<template>
<SidebarTabTemplate
:title="$t('sideToolbar.workflows')"
class="workflows-sidebar-tab bg-(--p-tree-background)"
class="workflows-sidebar-tab"
>
<template #tool-buttons>
<Button

View File

@@ -1,5 +1,5 @@
<template>
<div class="flex h-full flex-col overflow-auto bg-(--p-tree-background)">
<div class="flex h-full flex-col overflow-auto">
<div
class="flex items-center border-b border-(--p-divider-color) px-3 py-2"
>

View File

@@ -2094,71 +2094,72 @@
"cloudSurvey_steps_industry": "What's your primary industry?",
"cloudSurvey_steps_making": "What do you plan on making?",
"assetBrowser": {
"assets": "Assets",
"allCategory": "All {category}",
"allModels": "All Models",
"assetCollection": "Asset collection",
"checkpoints": "Checkpoints",
"assets": "Assets",
"baseModels": "Base models",
"browseAssets": "Browse Assets",
"noAssetsFound": "No assets found",
"tryAdjustingFilters": "Try adjusting your search or filters",
"loadingModels": "Loading {type}...",
"checkpoints": "Checkpoints",
"civitaiLinkExample": "<strong>Example:</strong> <a href=\"https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor</a>",
"civitaiLinkLabel": "Civitai model <span class=\"font-bold italic\">download</span> link",
"civitaiLinkPlaceholder": "Paste link here",
"confirmModelDetails": "Confirm Model Details",
"connectionError": "Please check your connection and try again",
"errorFileTooLarge": "File exceeds the maximum allowed size limit",
"errorFormatNotAllowed": "Only SafeTensor format is allowed",
"errorModelTypeNotSupported": "This model type is not supported",
"errorUnknown": "An unexpected error occurred",
"errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file",
"errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file",
"errorUploadFailed": "Failed to import asset. Please try again.",
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
"fileFormats": "File formats",
"fileName": "File Name",
"fileSize": "File Size",
"filterBy": "Filter by",
"findInLibrary": "Find it in the {type} section of the models library.",
"finish": "Finish",
"jobId": "Job ID",
"loadingModels": "Loading {type}...",
"modelAssociatedWithLink": "The model associated with the link you provided:",
"modelName": "Model Name",
"modelNamePlaceholder": "Enter a name for this model",
"modelTypeSelectorLabel": "What type of model is this?",
"modelTypeSelectorPlaceholder": "Select model type",
"modelUploaded": "Model imported! 🎉",
"noAssetsFound": "No assets found",
"noModelsInFolder": "No {type} available in this folder",
"uploadModel": "Import",
"uploadModelFromCivitai": "Import a model from Civitai",
"uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.",
"notSureLeaveAsIs": "Not sure? Just leave this as is",
"onlyCivitaiUrlsSupported": "Only Civitai URLs are supported",
"selectFrameworks": "Select Frameworks",
"selectModelType": "Select model type",
"selectProjects": "Select Projects",
"sortAZ": "A-Z",
"sortBy": "Sort by",
"sortingType": "Sorting Type",
"sortPopular": "Popular",
"sortRecent": "Recent",
"sortZA": "Z-A",
"tags": "Tags",
"tagsHelp": "Separate tags with commas",
"tagsPlaceholder": "e.g., models, checkpoint",
"tryAdjustingFilters": "Try adjusting your search or filters",
"unknown": "Unknown",
"upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.",
"upgradeToUnlockFeature": "Upgrade to unlock this feature",
"upload": "Import",
"uploadFailed": "Import failed",
"uploadingModel": "Importing model...",
"uploadModel": "Import",
"uploadModelDescription1": "Paste a Civitai model download link to add it to your library.",
"uploadModelDescription2": "Only links from <a href=\"https://civitai.com\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com</a> are supported at the moment",
"uploadModelDescription3": "Max file size: <strong>1 GB</strong>",
"civitaiLinkLabel": "Civitai model <span class=\"font-bold italic\">download</span> link",
"civitaiLinkPlaceholder": "Paste link here",
"civitaiLinkExample": "<strong>Example:</strong> <a href=\"https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor\" target=\"_blank\" class=\"text-muted-foreground\">https://civitai.com/api/download/models/833921?type=Model&format=SafeTensor</a>",
"confirmModelDetails": "Confirm Model Details",
"fileName": "File Name",
"fileSize": "File Size",
"modelName": "Model Name",
"modelNamePlaceholder": "Enter a name for this model",
"tags": "Tags",
"tagsPlaceholder": "e.g., models, checkpoint",
"tagsHelp": "Separate tags with commas",
"upload": "Import",
"uploadingModel": "Importing model...",
"uploadSuccess": "Model imported successfully!",
"uploadFailed": "Import failed",
"uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.",
"uploadModelFromCivitai": "Import a model from Civitai",
"uploadModelHelpVideo": "Upload Model Help Video",
"uploadModelHowDoIFindThis": "How do I find this?",
"modelAssociatedWithLink": "The model associated with the link you provided:",
"modelTypeSelectorLabel": "What type of model is this?",
"modelTypeSelectorPlaceholder": "Select model type",
"selectModelType": "Select model type",
"notSureLeaveAsIs": "Not sure? Just leave this as is",
"modelUploaded": "Model imported! 🎉",
"findInLibrary": "Find it in the {type} section of the models library.",
"finish": "Finish",
"upgradeToUnlockFeature": "Upgrade to unlock this feature",
"upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.",
"allModels": "All Models",
"allCategory": "All {category}",
"unknown": "Unknown",
"fileFormats": "File formats",
"baseModels": "Base models",
"filterBy": "Filter by",
"sortBy": "Sort by",
"sortAZ": "A-Z",
"sortZA": "Z-A",
"sortRecent": "Recent",
"sortPopular": "Popular",
"selectFrameworks": "Select Frameworks",
"selectProjects": "Select Projects",
"sortingType": "Sorting Type",
"errorFileTooLarge": "File exceeds the maximum allowed size limit",
"errorFormatNotAllowed": "Only SafeTensor format is allowed",
"errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file",
"errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file",
"errorModelTypeNotSupported": "This model type is not supported",
"errorUnknown": "An unexpected error occurred",
"errorUploadFailed": "Failed to import asset. Please try again.",
"uploadSuccess": "Model imported successfully!",
"ariaLabel": {
"assetCard": "{name} - {type} asset",
"loadingAsset": "Loading asset"
@@ -2291,4 +2292,4 @@
"recentReleases": "Recent releases",
"helpCenterMenu": "Help Center Menu"
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="flex gap-3 pt-2">
<div class="flex gap-3">
<SearchBox
:model-value="searchQuery"
:placeholder="$t('sideToolbar.searchAssets')"

View File

@@ -2,16 +2,20 @@
<div class="overflow-hidden">
<Tabs :value="activeTab">
<TabList class="scrollbar-hide overflow-x-auto">
<Tab v-if="hasCompatibilityIssues" value="warning" class="mr-6 p-2">
<Tab
v-if="hasCompatibilityIssues"
value="warning"
class="mr-6 p-2 font-inter"
>
<div class="flex items-center gap-1">
<span></span>
{{ importFailed ? $t('g.error') : $t('g.warning') }}
</div>
</Tab>
<Tab value="description" class="mr-6 p-2">
<Tab value="description" class="mr-6 p-2 font-inter">
{{ $t('g.description') }}
</Tab>
<Tab value="nodes" class="p-2">
<Tab value="nodes" class="p-2 font-inter">
{{ $t('g.nodes') }}
</Tab>
</TabList>