mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 23:50:08 +00:00
## Summary Small updates to the UI to make it more visually distinct from the graph nodes and improving the login button ## Changes - **What**: - Improve theme support with dynamic colors - Standardize surface colors/borders - Add shadows to all floating UI elements - Change side toolbar to be docked by default - Decrease side toolbar width - Change login button to icon only - Update C button to be more distinctive ## Review Focus - Theme tokens, I am not sure what the overall plan for how these tokens will be supported for custom user palettes. I've implemented a method where default variables are chosen that look nice over all existing themes, but they can be overridden. ## Screenshots Dark theme updated color <img width="958" height="615" alt="image" src="https://github.com/user-attachments/assets/a2c540cf-6c05-4ad3-996b-8b7b80df8d2d" /> Themed & updated menu button (active vs hover vs default) <img width="58" height="338" alt="github" src="https://github.com/user-attachments/assets/90244ee2-d5ee-4b26-9d99-f4b8439aa372" /><img width="58" height="338" alt="nord" src="https://github.com/user-attachments/assets/053e8e7b-d639-4b72-92d2-ec7e2167aab8" /><img width="58" height="338" alt="arc" src="https://github.com/user-attachments/assets/3caeb07b-d41b-4d88-83b4-ef8d45d4e5a6" /><img width="58" height="338" alt="solarized" src="https://github.com/user-attachments/assets/6ebf6afb-ec3a-436b-90eb-bda40be1c79f" /><img width="58" height="338" alt="light" src="https://github.com/user-attachments/assets/fbb7f96a-b722-40c5-86fa-a0732e166972" /><img width="58" height="338" alt="dark" src="https://github.com/user-attachments/assets/5459208b-9256-4c55-9373-169e9cd9f09a" /> With labels <img width="58" height="394" alt="labels" src="https://github.com/user-attachments/assets/f97ee354-35cd-42b8-896f-57ac39644c1d" /> Logged out (only visible on desktop) <img width="323" height="48" alt="logged out" src="https://github.com/user-attachments/assets/75b71420-0c1b-446f-8cdd-43c68674d346" /> Logged in <img width="355" height="48" alt="logged in" src="https://github.com/user-attachments/assets/324fd133-a793-49dd-844a-f93dd5d1effb" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6381-UI-color-updates-tweaks-29b6d73d3650816384d2ef5617a776a0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com>
276 lines
8.3 KiB
Vue
276 lines
8.3 KiB
Vue
<template>
|
|
<nav
|
|
ref="sideToolbarRef"
|
|
class="side-tool-bar-container flex h-full flex-col items-center bg-transparent [.floating-sidebar]:-mr-2"
|
|
:class="{
|
|
'small-sidebar': isSmall,
|
|
'connected-sidebar': isConnected,
|
|
'floating-sidebar': !isConnected,
|
|
'overflowing-sidebar': isOverflowing,
|
|
'border-r border-[var(--interface-stroke)] shadow-interface': isConnected
|
|
}"
|
|
>
|
|
<div
|
|
ref="contentMeasureRef"
|
|
:class="
|
|
isOverflowing
|
|
? 'side-tool-bar-container overflow-y-auto'
|
|
: 'flex flex-col h-full'
|
|
"
|
|
>
|
|
<div ref="topToolbarRef" :class="groupClasses">
|
|
<ComfyMenuButton />
|
|
<SidebarIcon
|
|
v-for="tab in tabs"
|
|
:key="tab.id"
|
|
:icon="tab.icon"
|
|
:icon-badge="tab.iconBadge"
|
|
:tooltip="tab.tooltip"
|
|
:tooltip-suffix="getTabTooltipSuffix(tab)"
|
|
:label="tab.label || tab.title"
|
|
:is-small="isSmall"
|
|
:selected="tab.id === selectedTab?.id"
|
|
:class="tab.id + '-tab-button'"
|
|
@click="onTabClick(tab)"
|
|
/>
|
|
<SidebarTemplatesButton />
|
|
</div>
|
|
|
|
<div ref="bottomToolbarRef" class="mt-auto" :class="groupClasses">
|
|
<SidebarLogoutIcon
|
|
v-if="userStore.isMultiUserServer"
|
|
:is-small="isSmall"
|
|
/>
|
|
<SidebarHelpCenterIcon :is-small="isSmall" />
|
|
<SidebarBottomPanelToggleButton :is-small="isSmall" />
|
|
<SidebarShortcutsToggleButton :is-small="isSmall" />
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useResizeObserver } from '@vueuse/core'
|
|
import { debounce } from 'es-toolkit/compat'
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
|
|
import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
|
|
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
|
|
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useTelemetry } from '@/platform/telemetry'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { useKeybindingStore } from '@/stores/keybindingStore'
|
|
import { useUserStore } from '@/stores/userStore'
|
|
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
|
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
import SidebarHelpCenterIcon from './SidebarHelpCenterIcon.vue'
|
|
import SidebarIcon from './SidebarIcon.vue'
|
|
import SidebarLogoutIcon from './SidebarLogoutIcon.vue'
|
|
import SidebarTemplatesButton from './SidebarTemplatesButton.vue'
|
|
|
|
const workspaceStore = useWorkspaceStore()
|
|
const settingStore = useSettingStore()
|
|
const userStore = useUserStore()
|
|
const commandStore = useCommandStore()
|
|
const canvasStore = useCanvasStore()
|
|
const sideToolbarRef = ref<HTMLElement>()
|
|
const contentMeasureRef = ref<HTMLElement>()
|
|
const topToolbarRef = ref<HTMLElement>()
|
|
const bottomToolbarRef = ref<HTMLElement>()
|
|
|
|
const isSmall = computed(
|
|
() => settingStore.get('Comfy.Sidebar.Size') === 'small'
|
|
)
|
|
const sidebarLocation = computed<'left' | 'right'>(() =>
|
|
settingStore.get('Comfy.Sidebar.Location')
|
|
)
|
|
const sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style'))
|
|
const isConnected = computed(
|
|
() =>
|
|
selectedTab.value ||
|
|
isOverflowing.value ||
|
|
sidebarStyle.value === 'connected'
|
|
)
|
|
|
|
const tabs = computed(() => workspaceStore.getSidebarTabs())
|
|
const selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
|
|
|
|
/**
|
|
* Handle sidebar tab icon click.
|
|
* - Emits UI button telemetry for known tabs
|
|
* - Delegates to the corresponding toggle command
|
|
*/
|
|
const onTabClick = async (item: SidebarTabExtension) => {
|
|
const telemetry = useTelemetry()
|
|
|
|
const isNodeLibraryTab = item.id === 'node-library'
|
|
const isModelLibraryTab = item.id === 'model-library'
|
|
const isWorkflowsTab = item.id === 'workflows'
|
|
const isAssetsTab = item.id === 'assets'
|
|
|
|
if (isNodeLibraryTab)
|
|
telemetry?.trackUiButtonClicked({
|
|
button_id: 'sidebar_tab_node_library_selected'
|
|
})
|
|
else if (isModelLibraryTab)
|
|
telemetry?.trackUiButtonClicked({
|
|
button_id: 'sidebar_tab_model_library_selected'
|
|
})
|
|
else if (isWorkflowsTab)
|
|
telemetry?.trackUiButtonClicked({
|
|
button_id: 'sidebar_tab_workflows_selected'
|
|
})
|
|
else if (isAssetsTab)
|
|
telemetry?.trackUiButtonClicked({
|
|
button_id: 'sidebar_tab_assets_media_selected'
|
|
})
|
|
|
|
await commandStore.commands
|
|
.find((cmd) => cmd.id === `Workspace.ToggleSidebarTab.${item.id}`)
|
|
?.function?.()
|
|
}
|
|
|
|
const keybindingStore = useKeybindingStore()
|
|
const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
|
|
const keybinding = keybindingStore.getKeybindingByCommandId(
|
|
`Workspace.ToggleSidebarTab.${tab.id}`
|
|
)
|
|
return keybinding ? ` (${keybinding.combo.toString()})` : ''
|
|
}
|
|
|
|
const isOverflowing = ref(false)
|
|
const groupClasses = computed(() =>
|
|
cn(
|
|
'sidebar-item-group pointer-events-auto flex flex-col items-center overflow-hidden flex-shrink-0' +
|
|
(isConnected.value ? '' : ' rounded-lg shadow-interface')
|
|
)
|
|
)
|
|
|
|
const ENTER_OVERFLOW_MARGIN = 20
|
|
const EXIT_OVERFLOW_MARGIN = 50
|
|
|
|
const checkOverflow = debounce(() => {
|
|
if (!sideToolbarRef.value || !topToolbarRef.value || !bottomToolbarRef.value)
|
|
return
|
|
|
|
const containerHeight = sideToolbarRef.value.clientHeight
|
|
const topHeight = topToolbarRef.value.scrollHeight
|
|
const bottomHeight = bottomToolbarRef.value.scrollHeight
|
|
const contentHeight = topHeight + bottomHeight
|
|
|
|
if (isOverflowing.value) {
|
|
isOverflowing.value = containerHeight < contentHeight + EXIT_OVERFLOW_MARGIN
|
|
} else {
|
|
isOverflowing.value =
|
|
containerHeight < contentHeight + ENTER_OVERFLOW_MARGIN
|
|
}
|
|
}, 16)
|
|
|
|
onMounted(() => {
|
|
if (!sideToolbarRef.value) return
|
|
|
|
const overflowObserver = useResizeObserver(
|
|
sideToolbarRef.value,
|
|
checkOverflow
|
|
)
|
|
|
|
checkOverflow()
|
|
|
|
onBeforeUnmount(() => {
|
|
overflowObserver.stop()
|
|
})
|
|
|
|
watch(
|
|
[isSmall, sidebarLocation],
|
|
async () => {
|
|
if (canvasStore.canvas) {
|
|
if (sidebarLocation.value === 'left') {
|
|
await nextTick()
|
|
canvasStore.canvas.fpsInfoLocation = [
|
|
sideToolbarRef.value?.getBoundingClientRect()?.right,
|
|
null
|
|
]
|
|
} else {
|
|
canvasStore.canvas.fpsInfoLocation = null
|
|
}
|
|
canvasStore.canvas.setDirty(false, true)
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
})
|
|
</script>
|
|
|
|
<style>
|
|
/* Global CSS variables for sidebar
|
|
* These variables need to be global (not scoped) because they are used by
|
|
* teleported components like WhatsNewPopup that render outside the sidebar
|
|
* but need to reference sidebar dimensions for proper positioning.
|
|
*/
|
|
:root {
|
|
--sidebar-padding: 4px;
|
|
--sidebar-icon-size: 1rem;
|
|
|
|
--sidebar-default-floating-width: 48px;
|
|
--sidebar-default-connected-width: calc(
|
|
var(--sidebar-default-floating-width) + var(--sidebar-padding) * 2
|
|
);
|
|
--sidebar-default-item-height: 56px;
|
|
|
|
--sidebar-small-floating-width: 48px;
|
|
--sidebar-small-connected-width: calc(
|
|
var(--sidebar-small-floating-width) + var(--sidebar-padding) * 2
|
|
);
|
|
--sidebar-small-item-height: 48px;
|
|
|
|
--sidebar-width: var(--sidebar-default-floating-width);
|
|
--sidebar-item-height: var(--sidebar-default-item-height);
|
|
}
|
|
|
|
:root:has(.side-tool-bar-container.small-sidebar) {
|
|
--sidebar-width: var(--sidebar-small-floating-width);
|
|
--sidebar-item-height: var(--sidebar-small-item-height);
|
|
}
|
|
|
|
:root:has(.side-tool-bar-container.connected-sidebar) {
|
|
--sidebar-width: var(--sidebar-default-connected-width);
|
|
}
|
|
|
|
:root:has(.side-tool-bar-container.small-sidebar.connected-sidebar) {
|
|
--sidebar-width: var(--sidebar-small-connected-width);
|
|
}
|
|
</style>
|
|
|
|
<style scoped>
|
|
@reference "tailwindcss";
|
|
|
|
.floating-sidebar {
|
|
padding: var(--sidebar-padding);
|
|
}
|
|
|
|
.floating-sidebar .sidebar-item-group {
|
|
border-color: var(--p-panel-border-color);
|
|
}
|
|
|
|
.connected-sidebar {
|
|
padding: var(--sidebar-padding) 0;
|
|
background-color: var(--comfy-menu-bg);
|
|
}
|
|
|
|
.sidebar-item-group {
|
|
background-color: var(--comfy-menu-bg);
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.overflowing-sidebar :deep(.comfy-menu-button-wrapper) {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
background-color: var(--comfy-menu-bg);
|
|
}
|
|
</style>
|