Style: Make components themeable (#5908)

## Summary

Replace color/dark-color pairs in components with design tokens to allow
for easy overriding.
<!-- Also standardizes the icon pattern to simplify the tailwind config.
-->

## Changes

- **What**: Token based colors, for now, mostly.
- **Breaking**: Got approval from Design to collapse some very similar
pairs of colors that seem to have diverged in implementations over time.
Some of the colors might be a little different, but we can tweak them
later.

## Review Focus

Still have quite a few places from which to remove `dark-theme`, but
this at least gets the theming much closer.
Need to decide if I want to keep going in here or cut this and do the
rest in a subsequent PR.

## 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-5908-WIP-Make-components-themeable-2816d73d365081ffbc05d189fe71084b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexander Brown
2025-10-06 16:27:08 -07:00
committed by GitHub
parent 51f0f111ea
commit e7745eb2be
50 changed files with 235 additions and 105 deletions

View File

@@ -24,9 +24,7 @@ export class VueNodeHelpers {
* Get locator for selected Vue node components (using visual selection indicators)
*/
get selectedNodes(): Locator {
return this.page.locator(
'[data-node-id].outline-black, [data-node-id].outline-white'
)
return this.page.locator('[data-node-id].outline-node-component-outline')
}
/**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -50,17 +50,17 @@ test.describe('Vue Node Collapse', () => {
// Check initial expanded state icon
let iconClass = await vueNode.getCollapseIconClass()
expect(iconClass).toContain('pi-chevron-down')
expect(iconClass).not.toContain('-rotate-90')
// Collapse and check icon
await vueNode.toggleCollapse()
iconClass = await vueNode.getCollapseIconClass()
expect(iconClass).toContain('pi-chevron-right')
expect(iconClass).toContain('-rotate-90')
// Expand and check icon
await vueNode.toggleCollapse()
iconClass = await vueNode.getCollapseIconClass()
expect(iconClass).toContain('pi-chevron-down')
expect(iconClass).not.toContain('-rotate-90')
})
test('should preserve title when collapsing/expanding', async ({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -128,12 +128,90 @@
--color-dark-elevation-2: rgba(from white r g b / 0.03);
}
:root {
--backdrop: var(--color-white);
--dialog-surface: var(--color-neutral-200);
--node-component-border: var(--color-gray-400);
--node-component-executing: var(--color-blue-500);
--node-component-header: var(--fg-color);
--node-component-header-icon: var(--color-stone-200);
--node-component-header-surface: var(--color-white);
--node-component-outline: var(--color-black);
--node-component-ring: rgb(from var(--color-gray-500) r g b / 50%);
--node-component-slot-dot-outline-opacity-mult: 1;
--node-component-slot-dot-outline-opacity: 5%;
--node-component-slot-dot-outline: var(--color-black);
--node-component-slot-text: var(--color-stone-200);
--node-component-surface-highlight: var(--color-stone-100);
--node-component-surface-hovered: var(--color-charcoal-400);
--node-component-surface-selected: var(--color-charcoal-200);
--node-component-surface: var(--color-white);
--node-component-tooltip: var(--color-charcoal-700);
--node-component-tooltip-border: var(--color-sand-100);
--node-component-tooltip-surface: var(--color-white);
--node-component-widget-input: var(--fg-color);
--node-component-widget-input-surface: rgb(from var(--color-zinc-500) r g b / 10%);
--node-component-widget-skeleton-surface: var(--color-zinc-300);
--node-stroke: var(--color-stone-100);
}
.dark-theme {
--backdrop: var(--color-neutral-900);
--dialog-surface: var(--color-neutral-700);
--node-component-border: var(--color-stone-200);
--node-component-header-icon: var(--color-slate-300);
--node-component-header-surface: var(--color-charcoal-800);
--node-component-outline: var(--color-white);
--node-component-ring: rgb(var(--color-gray-500) / 20%);
--node-component-slot-dot-outline-opacity: 10%;
--node-component-slot-dot-outline: var(--color-white);
--node-component-slot-text: var(--color-slate-200);
--node-component-surface-highlight: var(--color-slate-100);
--node-component-surface-hovered: var(--color-charcoal-400);
--node-component-surface-selected: var(--color-charcoal-200);
--node-component-surface: var(--color-charcoal-800);
--node-component-tooltip: var(--color-white);
--node-component-tooltip-border: var(--color-slate-300);
--node-component-tooltip-surface: var(--color-charcoal-800);
--node-component-widget-skeleton-surface: var(--color-zinc-800);
--node-stroke: var(--color-slate-100);
}
@theme inline {
--color-node-component-surface: var(--color-charcoal-600);
--color-node-component-surface-highlight: var(--color-slate-100);
--color-node-component-surface-hovered: var(--color-charcoal-400);
--color-node-component-surface-selected: var(--color-charcoal-200);
--color-node-stroke: var(--color-stone-100);
--color-backdrop: var(--backdrop);
--color-dialog-surface: var(--dialog-surface);
--color-node-component-border: var(--node-component-border);
--color-node-component-executing: var(--node-component-executing);
--color-node-component-header: var(--node-component-header);
--color-node-component-header-icon: var(--node-component-header-icon);
--color-node-component-header-surface: var(--node-component-header-surface);
--color-node-component-outline: var(--node-component-outline);
--color-node-component-ring: var(--node-component-ring);
--color-node-component-slot-dot-outline: rgb(
from var(--node-component-slot-dot-outline) r g b /
calc(
var(--node-component-slot-dot-outline-opacity) *
var(--node-component-slot-dot-outline-opacity-mult)
)
);
--color-node-component-slot-text: var(--node-component-slot-text);
--color-node-component-surface-highlight: var(
--node-component-surface-highlight
);
--color-node-component-surface-hovered: var(--node-component-surface-hovered);
--color-node-component-surface-selected: var(--component-surface-selected);
--color-node-component-surface: var(--node-component-surface);
--color-node-component-tooltip: var(--node-component-tooltip);
--color-node-component-tooltip-border: var(--node-component-tooltip-border);
--color-node-component-tooltip-surface: var(--node-component-tooltip-surface);
--color-node-component-widget-input: var(--node-component-widget-input);
--color-node-component-widget-input-surface: var(
--node-component-widget-input-surface
);
--color-node-component-widget-skeleton-surface: var(
--node-component-widget-skeleton-surface
);
--color-node-stroke: var(--node-stroke);
}
@custom-variant dark-theme {

View File

@@ -128,7 +128,7 @@
<!-- Title -->
<span
v-if="isLoading"
class="inline-block h-8 w-48 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
class="inline-block h-8 w-48 bg-dialog-surface rounded animate-pulse"
></span>
<!-- Template Cards Grid -->
@@ -148,7 +148,7 @@
<CardTop ratio="landscape">
<template #default>
<div
class="w-full h-full bg-neutral-200 dark-theme:bg-neutral-700 animate-pulse"
class="w-full h-full bg-dialog-surface animate-pulse"
></div>
</template>
</CardTop>
@@ -157,10 +157,10 @@
<CardBottom>
<div class="px-4 py-3">
<div
class="h-6 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse mb-2"
class="h-6 bg-dialog-surface rounded animate-pulse mb-2"
></div>
<div
class="h-4 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
class="h-4 bg-dialog-surface rounded animate-pulse"
></div>
</div>
</CardBottom>
@@ -323,7 +323,7 @@
<CardTop ratio="square">
<template #default>
<div
class="w-full h-full bg-neutral-200 dark-theme:bg-neutral-700 animate-pulse"
class="w-full h-full bg-dialog-surface animate-pulse"
></div>
</template>
</CardTop>
@@ -332,10 +332,10 @@
<CardBottom>
<div class="px-4 py-3">
<div
class="h-6 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse mb-2"
class="h-6 bg-dialog-surface rounded animate-pulse mb-2"
></div>
<div
class="h-4 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
class="h-4 bg-dialog-surface rounded animate-pulse"
></div>
</div>
</CardBottom>

View File

@@ -13,7 +13,7 @@
<div class="relative">
<span
v-if="shouldShowStatusIndicator"
class="group-hover:hidden absolute font-bold text-2xl top-1/2 left-1/2 -translate-1/2 z-10 bg-(--comfy-menu-secondary-bg) w-4"
class="group-hover:hidden absolute font-bold text-2xl top-1/2 left-1/2 -translate-1/2 z-10 bg-(--comfy-menu-bg) w-4"
></span
>
<Button

View File

@@ -246,7 +246,7 @@ onBeforeUnmount(() => {
/>
<div
v-if="filteredActive.length"
class="pt-1 pb-4 border-b-1 border-sand-100 dark-theme:border-charcoal-600"
class="pt-1 pb-4 border-b-1 border-node-component-border"
>
<div class="flex py-0 px-4 justify-between">
<div class="text-slate-100 text-[9px] font-semibold uppercase">
@@ -302,7 +302,7 @@ onBeforeUnmount(() => {
</div>
<div
v-if="recommendedWidgets.length"
class="justify-center flex py-4 border-t-1 border-sand-100 dark-theme:border-charcoal-600"
class="justify-center flex py-4 border-t-1 border-node-component-border"
>
<Button
size="small"

View File

@@ -16,7 +16,7 @@ defineEmits<{
function classes() {
return cn(
'flex py-1 pr-4 pl-0 break-all rounded items-center gap-1',
'bg-pure-white dark-theme:bg-charcoal-800',
'bg-node-component-surface',
props.isDraggable
? 'drag-handle cursor-grab [.is-draggable]:cursor-grabbing'
: ''

View File

@@ -10,7 +10,7 @@ export function showSubgraphNodeDialog() {
position: 'topright',
pt: {
root: {
class: 'bg-pure-white dark-theme:bg-charcoal-800 mt-22'
class: 'bg-node-component-surface mt-22'
},
header: {
class: 'h-8 text-xs ml-3'

View File

@@ -45,7 +45,7 @@
</Button>
<hr
class="absolute top-5 bg-[#E1DED5] dark-theme:bg-[#262729] h-[1px] border-0"
class="absolute top-5 bg-node-component-border h-px border-0"
:style="{
width: containerStyles.width
}"

View File

@@ -71,14 +71,14 @@ const hasSlotError = computed(() => {
const errorClassesDot = computed(() => {
return hasSlotError.value
? 'ring-2 ring-error dark-theme:ring-error ring-offset-0 rounded-full'
? 'ring-2 ring-error ring-offset-0 rounded-full'
: ''
})
const labelClasses = computed(() =>
hasSlotError.value
? 'text-error dark-theme:text-error font-medium'
: 'dark-theme:text-slate-200 text-stone-200'
? 'text-error font-medium'
: 'text-node-component-slot-text'
)
const renderError = ref<string | null>(null)

View File

@@ -8,12 +8,12 @@
:data-node-id="nodeData.id"
:class="
cn(
'bg-white dark-theme:bg-charcoal-800',
'bg-node-component-surface',
'lg-node absolute rounded-2xl touch-none',
'border-1 border-solid border-gray-400 dark-theme:border-stone-200',
'border-1 border-solid border-node-component-border',
// hover (only when node should handle events)
shouldHandleNodePointerEvents &&
'hover:ring-7 ring-gray-500/50 dark-theme:ring-gray-500/20',
'hover:ring-7 ring-node-component-ring',
'outline-transparent -outline-offset-2 outline-2',
borderClass,
outlineClass,
@@ -274,8 +274,7 @@ const hasCustomContent = computed(() => {
})
// Computed classes and conditions for better reusability
const separatorClasses =
'bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full lod-toggle'
const separatorClasses = 'bg-node-component-border h-px mx-0 w-full lod-toggle'
const progressClasses = 'h-2 bg-primary-500 transition-all duration-300'
const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
@@ -287,17 +286,17 @@ const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
const borderClass = computed(() => {
return (
(hasAnyError.value && 'border-error dark-theme:border-error') ||
(executing.value && 'border-blue-500')
(hasAnyError.value && 'border-error') ||
(executing.value && 'border-node-executing')
)
})
const outlineClass = computed(() => {
return (
return cn(
isSelected.value &&
((hasAnyError.value && 'outline-error dark-theme:outline-error') ||
(executing.value && 'outline-blue-500 dark-theme:outline-blue-500') ||
'outline-black dark-theme:outline-white')
((hasAnyError.value && 'outline-error ') ||
(executing.value && 'outline-node-executing') ||
'outline-node-component-outline')
)
})

View File

@@ -1,13 +1,11 @@
<template>
<div class="scale-75">
<div
class="bg-white dark-theme:bg-charcoal-800 lg-node absolute rounded-2xl border border-solid border-sand-100 dark-theme:border-charcoal-600 outline-transparent -outline-offset-2 outline-2 pointer-events-none"
class="bg-node-component-surface lg-node absolute rounded-2xl border border-solid border-node-component-border outline-transparent -outline-offset-2 outline-2 pointer-events-none"
>
<NodeHeader :node-data="nodeData" :readonly="readonly" />
<div
class="bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full mb-4"
/>
<div class="bg-node-component-border h-px mx-0 w-full mb-4" />
<div class="flex flex-col gap-4 pb-4">
<NodeSlots

View File

@@ -1,5 +1,5 @@
<template>
<div
class="lod-fallback absolute inset-0 w-full h-full bg-zinc-300 dark-theme:bg-zinc-800"
class="lod-fallback absolute inset-0 w-full h-full bg-node-component-widget-skeleton-surface"
></div>
</template>

View File

@@ -181,11 +181,11 @@ describe('NodeHeader.vue', () => {
it('renders correct chevron icon based on collapsed prop', async () => {
const wrapper = mountHeader({ collapsed: false })
const expandedIcon = wrapper.get('i')
expect(expandedIcon.classes()).toContain('pi-chevron-down')
expect(expandedIcon.classes()).not.toContain('-rotate-90')
await wrapper.setProps({ collapsed: true })
const collapsedIcon = wrapper.get('i')
expect(collapsedIcon.classes()).toContain('pi-chevron-right')
expect(collapsedIcon.classes()).toContain('-rotate-90')
})
describe('Tooltips', () => {

View File

@@ -4,24 +4,37 @@
</div>
<div
v-else
class="lg-node-header p-4 rounded-t-2xl cursor-move w-full"
:class="
cn(
'lg-node-header p-4 rounded-t-2xl cursor-move w-full bg-node-component-header-surface text-node-component-header',
collapsed && 'rounded-2xl'
)
"
:style="headerStyle"
:data-testid="`node-header-${nodeData?.id || ''}`"
@dblclick="handleDoubleClick"
>
<div class="flex items-center justify-between gap-2.5 relative">
<!-- Collapse/Expand Button -->
<button
class="bg-transparent border-transparent flex items-center lod-toggle"
data-testid="node-collapse-button"
@click.stop="handleCollapse"
@dblclick.stop
>
<i
:class="collapsed ? 'pi pi-chevron-right' : 'pi pi-chevron-down'"
class="text-xs leading-none relative top-px text-stone-200 dark-theme:text-slate-300"
></i>
</button>
<div class="flex items-center lod-toggle shrink-0 px-0.5">
<IconButton
size="fit-content"
type="transparent"
data-testid="node-collapse-button"
@click.stop="handleCollapse"
@dblclick.stop
>
<i
:class="
cn(
'icon-[lucide--chevron-down] size-5 transition-transform',
collapsed && '-rotate-90'
)
"
class="text-xs leading-none relative top-px text-node-component-header-icon"
></i>
</IconButton>
</div>
<!-- Node Title -->
<div
@@ -38,7 +51,7 @@
/>
<i-lucide:pin
v-if="isPinned"
class="w-5 h-5 text-stone-200 dark-theme:text-slate-300"
class="size-5 text-node-component-header-icon"
data-testid="node-pin-indicator"
/>
</div>
@@ -48,12 +61,13 @@
v-tooltip.top="enterSubgraphTooltipConfig"
size="sm"
type="transparent"
class="text-stone-200 dark-theme:text-slate-300"
data-testid="subgraph-enter-button"
@click.stop="handleEnterSubgraph"
@dblclick.stop
>
<i class="pi pi-external-link"></i>
<i
class="icon-[lucide--picture-in-picture] size-5 text-node-component-header-icon"
></i>
</IconButton>
</div>
<LODFallback />
@@ -79,6 +93,7 @@ import {
getLocatorIdFromNodeData,
getNodeByLocatorId
} from '@/utils/graphTraversalUtil'
import { cn } from '@/utils/tailwindUtil'
import LODFallback from './LODFallback.vue'

View File

@@ -5,7 +5,7 @@
<!-- Slot Name -->
<span
v-if="!dotOnly"
class="whitespace-nowrap text-sm font-normal dark-theme:text-slate-200 text-stone-200 lod-toggle"
class="whitespace-nowrap text-sm font-normal text-node-component-slot-text lod-toggle"
>
{{ slotData.localized_name || slotData.name || `Output ${index}` }}
</span>

View File

@@ -28,11 +28,11 @@ defineExpose({
:style="{ backgroundColor: color }"
:class="
cn(
'bg-[#5B5E7D] rounded-full',
'bg-slate-300 rounded-full',
'transition-all duration-150',
'cursor-crosshair',
'border border-solid border-black/5 dark-theme:border-white/10',
'group-hover/slot:border-black/20 dark-theme:group-hover/slot:border-white/50 group-hover/slot:scale-125',
'border border-solid border-node-component-slot-dot-outline',
'group-hover/slot:[--node-component-slot-dot-outline-opacity-mult:5] group-hover/slot:scale-125',
multi ? 'w-3 h-6' : 'size-3'
)
"

View File

@@ -163,14 +163,14 @@ export function useNodeTooltips(nodeType: MaybeRef<string>) {
pt: {
text: {
class:
'border-sand-100 bg-pure-white dark-theme:bg-charcoal-800 border dark-theme:border-slate-300 rounded-md px-4 py-2 text-charcoal-700 dark-theme:text-pure-white text-sm font-normal leading-tight max-w-75 shadow-none'
'border-node-component-tooltip-border bg-node-component-tooltip-surface border rounded-md px-4 py-2 text-node-component-tooltip text-sm font-normal leading-tight max-w-75 shadow-none'
},
arrow: ({ context }: TooltipPassThroughMethodOptions) => ({
class: cn(
context.top && 'border-t-sand-100 dark-theme:border-t-slate-300',
context.bottom && 'border-b-sand-100 dark-theme:border-b-slate-300',
context.left && 'border-l-sand-100 dark-theme:border-l-slate-300',
context.right && 'border-r-sand-100 dark-theme:border-r-slate-300'
context.top && 'border-t-node-component-tooltip-border',
context.bottom && 'border-b-node-component-tooltip-border',
context.left && 'border-l-node-component-tooltip-border ',
context.right && 'border-r-node-component-tooltip-border'
)
})
} as TooltipDirectivePassThroughOptions

View File

@@ -145,7 +145,7 @@
:style="{ borderColor: '#262729' }"
>
<div
class="border border-dashed p-1 rounded-md transition-colors duration-200 hover:border-[#5B5E7D]"
class="border border-dashed p-1 rounded-md transition-colors duration-200 hover:border-slate-300"
:style="{ borderColor: '#262729' }"
>
<div class="flex flex-col items-center gap-2 w-full py-4">

View File

@@ -38,14 +38,13 @@ const chevronClass = computed(() =>
})
)
const theButtonStyle = computed(() => [
'bg-transparent border-0 outline-none text-zinc-400',
{
'hover:bg-zinc-500/30 hover:text-black hover:dark-theme:text-white cursor-pointer':
const theButtonStyle = computed(() =>
cn('bg-transparent border-0 outline-none text-zinc-400', {
'hover:bg-node-component-widget-input-surface/30 cursor-pointer':
!props.disabled,
'cursor-not-allowed': props.disabled
}
])
})
)
</script>
<template>

View File

@@ -36,7 +36,7 @@ const searchQuery = defineModel<string>('searchQuery')
<template>
<div
class="w-103 h-[640px] pt-4 bg-white dark-theme:bg-charcoal-800 rounded-lg outline outline-offset-[-1px] outline-sand-100 dark-theme:outline-zinc-800 flex flex-col"
class="w-103 max-h-[640px] pt-4 bg-node-component-surface rounded-lg outline outline-offset-[-1px] outline-node-component-border flex flex-col"
>
<!-- Filter -->
<FormDropdownMenuFilter
@@ -67,7 +67,7 @@ const searchQuery = defineModel<string>('searchQuery')
"
>
<div
class="absolute top-0 inset-x-3 h-5 bg-gradient-to-b from-white dark-theme:from-neutral-900 to-transparent pointer-events-none z-10"
class="absolute top-0 inset-x-3 h-5 bg-gradient-to-b from-backdrop to-transparent pointer-events-none z-10"
/>
<div
v-if="items.length === 0"

View File

@@ -17,7 +17,7 @@ defineProps<{
<div class="relative h-6 flex items-center mr-4">
<p
v-if="widget.name"
class="text-sm text-stone-200 dark-theme:text-slate-200 font-normal flex-1 truncate w-20 lod-toggle"
class="text-sm text-node-component-slot-text font-normal flex-1 truncate w-20 lod-toggle"
>
{{ widget.label || widget.name }}
</p>

View File

@@ -2,15 +2,13 @@ import { cn } from '@/utils/tailwindUtil'
export const WidgetInputBaseClass = cn([
// Background
'bg-zinc-500/10',
'bg-node-component-widget-input-surface',
'text-node-component-widget-input',
// Outline
'border-none',
'outline',
'outline-1',
'outline-offset-[-1px]',
'outline-zinc-300/10',
'outline outline-offset-[-1px] outline-zinc-300/10',
// Rounded
'!rounded-lg',
'rounded-lg',
// Hover
'hover:outline-blue-500/80'
])

View File

@@ -12,6 +12,16 @@ import { downloadBlob, uploadFile } from '@/scripts/utils'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
const THEME_PROPERTY_MAP = {
NODE_BOX_OUTLINE_COLOR: 'node-component-border',
NODE_DEFAULT_BGCOLOR: 'node-component-surface',
NODE_DEFAULT_BOXCOLOR: 'node-component-header-icon',
NODE_DEFAULT_COLOR: 'node-component-header-surface',
NODE_TITLE_COLOR: 'node-component-header',
WIDGET_BGCOLOR: 'node-component-widget-input-surface',
WIDGET_TEXT_COLOR: 'node-component-widget-input'
} as const satisfies Partial<Record<keyof Colors['litegraph_base'], string>>
export const useColorPaletteService = () => {
const colorPaletteStore = useColorPaletteStore()
const settingStore = useSettingStore()
@@ -78,6 +88,40 @@ export const useColorPaletteService = () => {
Object.assign(LGraphCanvas.link_type_colors, types, linkColorPalette)
}
function validThemeProp(
propertyMaybe: unknown
): propertyMaybe is keyof typeof THEME_PROPERTY_MAP {
return (
(propertyMaybe as keyof typeof THEME_PROPERTY_MAP) in THEME_PROPERTY_MAP
)
}
function loadLitegraphForVueNodes(
palette: Colors['litegraph_base'],
colorPaletteId: string
) {
if (!palette) return
const rootStyle = document.getElementById('vue-app')?.style
if (!rootStyle) return
for (const themeVar of Object.keys(THEME_PROPERTY_MAP)) {
if (!validThemeProp(themeVar)) {
continue
}
const cssVar = THEME_PROPERTY_MAP[themeVar]
if (colorPaletteId === 'dark' || colorPaletteId === 'light') {
rootStyle.removeProperty(`--${cssVar}`)
continue
}
const valueMaybe = palette[themeVar]
if (valueMaybe) {
rootStyle.setProperty(`--${cssVar}`, valueMaybe)
} else {
rootStyle.removeProperty(`--${cssVar}`)
}
}
}
/**
* Loads the LiteGraph color palette.
*
@@ -120,20 +164,19 @@ export const useColorPaletteService = () => {
* @param comfyColorPalette - The palette to set.
*/
const loadComfyColorPalette = (comfyColorPalette: Colors['comfy_base']) => {
if (comfyColorPalette) {
const rootStyle = document.documentElement.style
for (const [key, value] of Object.entries(comfyColorPalette)) {
rootStyle.setProperty('--' + key, value)
}
const backgroundImage = settingStore.get('Comfy.Canvas.BackgroundImage')
if (backgroundImage) {
rootStyle.setProperty(
'--bg-img',
`url('${backgroundImage}') no-repeat center /cover`
)
} else {
rootStyle.removeProperty('--bg-img')
}
if (!comfyColorPalette) return
const rootStyle = document.documentElement.style
for (const [key, value] of Object.entries(comfyColorPalette)) {
rootStyle.setProperty('--' + key, value)
}
const backgroundImage = settingStore.get('Comfy.Canvas.BackgroundImage')
if (backgroundImage) {
rootStyle.setProperty(
'--bg-img',
`url('${backgroundImage}') no-repeat center /cover`
)
} else {
rootStyle.removeProperty('--bg-img')
}
}
@@ -151,6 +194,10 @@ export const useColorPaletteService = () => {
const completedPalette = colorPaletteStore.completePalette(colorPalette)
loadLinkColorPalette(completedPalette.colors.node_slot)
loadLiteGraphColorPalette(completedPalette.colors.litegraph_base)
loadLitegraphForVueNodes(
completedPalette.colors.litegraph_base,
colorPaletteId
)
loadComfyColorPalette(completedPalette.colors.comfy_base)
app.canvas.setDirty(true, true)

View File

@@ -121,8 +121,7 @@ export const useDialogService = () => {
pt: {
pcCloseButton: {
root: {
class:
'bg-gray-500 dark-theme:bg-neutral-700 w-9 h-9 p-1.5 rounded-full text-white'
class: 'bg-dialog-surface w-9 h-9 p-1.5 rounded-full text-white'
}
},
header: { class: 'py-0! px-6 m-0! h-[68px]' },
@@ -469,7 +468,7 @@ export const useDialogService = () => {
pcCloseButton: {
root: {
class:
'!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5 bg-gray-500 dark-theme:bg-neutral-700 text-white'
'!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5 bg-dialog-surface text-white'
}
}
},

View File

@@ -6,7 +6,7 @@
"
class="inline-flex items-center gap-1 rounded-2xl text-xs py-1"
:class="{
'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill,
'bg-dialog-surface px-1.5': fill,
'cursor-pointer': !isDisabled,
'cursor-not-allowed opacity-60': isDisabled
}"

View File

@@ -171,8 +171,7 @@ describe('LGraphNode', () => {
mockData.mockNodeIds = new Set(['test-node-123'])
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
expect(wrapper.classes()).toContain('outline-2')
expect(wrapper.classes()).toContain('outline-black')
expect(wrapper.classes()).toContain('dark-theme:outline-white')
expect(wrapper.classes()).toContain('outline-node-component-outline')
})
it('should apply executing animation when executing prop is true', () => {