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>
@@ -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')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -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 ({
|
||||
|
||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
: ''
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
])
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}"
|
||||
|
||||
@@ -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', () => {
|
||||