[Style] Compact Modern Nodes (#6687)
## Summary Simple and clean is the way that we're making the nodes tonight. ## Changes - **What**: Smaller minimum widths for nodes and labels - **What**: Smaller font for the labels - **What**: Removed outlines for widgets - **What**: Fixes a text/background issue with buttons on widgets - **What**: Smaller header - **What**: Less padding within the node itself ## Review Focus Check out the new styles and how they align with the Designs. ## Screenshots | Before | After | | --- | --- | | <img width="542" height="486" alt="image" src="https://github.com/user-attachments/assets/41fe9801-7a43-49ac-87fc-36d3b2ee82fb" /> | <img width="411" height="388" alt="image" src="https://github.com/user-attachments/assets/a7c21120-bf67-4039-86b3-c348bcc4341b" /> | <!-- Add screenshots or video recording to help explain your changes --> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6687-Style-Compact-Modern-Nodes-2aa6d73d365081c48db3c5491c556dc9) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 82 KiB |
@@ -10,11 +10,11 @@
|
||||
/>
|
||||
|
||||
<!-- Slot Name -->
|
||||
<div class="relative">
|
||||
<div class="relative h-full flex items-center">
|
||||
<span
|
||||
v-if="!dotOnly"
|
||||
:class="
|
||||
cn('whitespace-nowrap text-sm font-normal lod-toggle', labelClasses)
|
||||
cn('whitespace-nowrap text-xs font-normal lod-toggle', labelClasses)
|
||||
"
|
||||
>
|
||||
{{ slotData.localized_name || slotData.name || `Input ${index}` }}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
/>
|
||||
|
||||
<template v-if="!isCollapsed">
|
||||
<div class="relative mb-4">
|
||||
<div class="relative mb-1">
|
||||
<div :class="separatorClasses" />
|
||||
<!-- Progress bar for executing state -->
|
||||
<div
|
||||
@@ -101,7 +101,7 @@
|
||||
|
||||
<!-- Node Body - rendered based on LOD level and collapsed state -->
|
||||
<div
|
||||
class="flex min-h-min min-w-min flex-1 flex-col gap-4 pb-4"
|
||||
class="flex min-h-min min-w-min flex-1 flex-col gap-1 pb-2"
|
||||
:data-testid="`node-body-${nodeData.id}`"
|
||||
>
|
||||
<!-- Slots only rendered at full detail -->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-else
|
||||
:class="
|
||||
cn(
|
||||
'lg-node-header p-4 rounded-t-2xl w-full min-w-50',
|
||||
'lg-node-header py-2 pl-2 pr-3 text-sm rounded-t-2xl w-full min-w-50',
|
||||
'text-node-component-header',
|
||||
collapsed && 'rounded-2xl'
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div v-else :class="cn('flex justify-between', unifiedWrapperClass)">
|
||||
<div
|
||||
v-if="filteredInputs.length"
|
||||
:class="cn('flex flex-col gap-1', unifiedDotsClass)"
|
||||
:class="cn('flex flex-col', unifiedDotsClass)"
|
||||
>
|
||||
<InputSlot
|
||||
v-for="(input, index) in filteredInputs"
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<div
|
||||
v-if="nodeData?.outputs?.length"
|
||||
:class="cn('ml-auto flex flex-col gap-1', unifiedDotsClass)"
|
||||
:class="cn('ml-auto flex flex-col', unifiedDotsClass)"
|
||||
>
|
||||
<OutputSlot
|
||||
v-for="(output, index) in nodeData.outputs"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-else
|
||||
:class="
|
||||
cn(
|
||||
'lg-node-widgets flex flex-col has-[.widget-expands]:flex-1 gap-2 pr-3',
|
||||
'lg-node-widgets flex flex-col has-[.widget-expands]:flex-1 gap-1 pr-3',
|
||||
shouldHandleNodePointerEvents
|
||||
? 'pointer-events-auto'
|
||||
: 'pointer-events-none'
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-if="renderError" class="node-error p-1 text-xs text-red-500">⚠️</div>
|
||||
<div v-else v-tooltip.right="tooltipConfig" :class="slotWrapperClass">
|
||||
<div class="relative">
|
||||
<div class="relative h-full flex items-center">
|
||||
<!-- Slot Name -->
|
||||
<span
|
||||
v-if="!dotOnly"
|
||||
class="lod-toggle text-sm font-normal whitespace-nowrap text-node-component-slot-text"
|
||||
class="lod-toggle text-xs font-normal whitespace-nowrap text-node-component-slot-text"
|
||||
>
|
||||
{{ slotData.localized_name || slotData.name || `Output ${index}` }}
|
||||
</span>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
filterWidgetProps
|
||||
} from '@/utils/widgetPropFilter'
|
||||
|
||||
import { useNumberWidgetButtonPt } from '../composables/useNumberWidgetButtonPt'
|
||||
import { WidgetInputBaseClass } from './layout'
|
||||
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
||||
|
||||
@@ -79,57 +78,50 @@ const buttonTooltip = computed(() => {
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const inputNumberPt = useNumberWidgetButtonPt({
|
||||
roundedLeft: true,
|
||||
roundedRight: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetLayoutField :widget>
|
||||
<div v-tooltip="buttonTooltip">
|
||||
<InputNumber
|
||||
v-model="localValue"
|
||||
v-bind="filteredProps"
|
||||
button-layout="horizontal"
|
||||
size="small"
|
||||
:step="stepValue"
|
||||
:use-grouping="useGrouping"
|
||||
:class="cn(WidgetInputBaseClass, 'w-full text-xs')"
|
||||
:aria-label="widget.name"
|
||||
:show-buttons="!buttonsDisabled"
|
||||
:pt="inputNumberPt"
|
||||
@update:model-value="onChange"
|
||||
>
|
||||
<template #incrementicon>
|
||||
<span
|
||||
class="pi pi-plus text-sm text-component-node-foreground-secondary"
|
||||
/>
|
||||
</template>
|
||||
<template #decrementicon>
|
||||
<span
|
||||
class="pi pi-minus text-sm text-component-node-foreground-secondary"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
<InputNumber
|
||||
v-model="localValue"
|
||||
v-tooltip="buttonTooltip"
|
||||
v-bind="filteredProps"
|
||||
fluid
|
||||
button-layout="horizontal"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
:step="stepValue"
|
||||
:use-grouping="useGrouping"
|
||||
:class="cn(WidgetInputBaseClass, 'grow text-xs')"
|
||||
:aria-label="widget.name"
|
||||
:show-buttons="!buttonsDisabled"
|
||||
:pt="{
|
||||
root: {
|
||||
class: '[&>input]:bg-transparent [&>input]:border-0'
|
||||
},
|
||||
decrementButton: {
|
||||
class: 'w-8 border-0'
|
||||
},
|
||||
incrementButton: {
|
||||
class: 'w-8 border-0'
|
||||
}
|
||||
}"
|
||||
@update:model-value="onChange"
|
||||
>
|
||||
<template #incrementicon>
|
||||
<span class="pi pi-plus text-sm" />
|
||||
</template>
|
||||
<template #decrementicon>
|
||||
<span class="pi pi-minus text-sm" />
|
||||
</template>
|
||||
</InputNumber>
|
||||
</WidgetLayoutField>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-inputnumber-input) {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--component-node-border);
|
||||
border-top: transparent;
|
||||
border-bottom: transparent;
|
||||
height: 1.625rem;
|
||||
margin: 1px 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.p-inputnumber-button.p-disabled .pi),
|
||||
:deep(.p-inputnumber-button.p-disabled .p-icon) {
|
||||
color: var(--color-node-icon-disabled) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<WidgetLayoutField :widget="widget">
|
||||
<div
|
||||
:class="
|
||||
cn(WidgetInputBaseClass, 'flex items-center gap-2 w-full pl-4 pr-2')
|
||||
"
|
||||
>
|
||||
<div :class="cn(WidgetInputBaseClass, 'flex items-center gap-2 pl-3 pr-2')">
|
||||
<Slider
|
||||
:model-value="[localValue]"
|
||||
v-bind="filteredProps"
|
||||
@@ -24,7 +20,6 @@
|
||||
size="small"
|
||||
pt:pc-input-text:root="min-w-full bg-transparent border-none text-center"
|
||||
class="w-16"
|
||||
:show-buttons="!buttonsDisabled"
|
||||
:pt="sliderNumberPt"
|
||||
@update:model-value="handleNumberInputUpdate"
|
||||
/>
|
||||
@@ -107,14 +102,6 @@ const stepValue = computed(() => {
|
||||
return 1 / Math.pow(10, precision.value)
|
||||
})
|
||||
|
||||
const buttonsDisabled = computed(() => {
|
||||
const currentValue = localValue.value ?? 0
|
||||
return (
|
||||
!Number.isFinite(currentValue) ||
|
||||
Math.abs(currentValue) > Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
})
|
||||
|
||||
const sliderNumberPt = useNumberWidgetButtonPt({
|
||||
roundedLeft: true,
|
||||
roundedRight: true
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
size="small"
|
||||
:pt="{
|
||||
option: 'text-xs',
|
||||
dropdownIcon: 'text-component-node-foreground-secondary'
|
||||
dropdown: 'w-8'
|
||||
}"
|
||||
data-capture-wheel="true"
|
||||
@update:model-value="onChange"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<WidgetLayoutField :widget="widget">
|
||||
<WidgetLayoutField :widget>
|
||||
<ToggleSwitch
|
||||
v-model="localValue"
|
||||
v-bind="filteredProps"
|
||||
class="ml-auto block"
|
||||
:aria-label="widget.name"
|
||||
@update:model-value="onChange"
|
||||
/>
|
||||
@@ -42,13 +43,3 @@ const filteredProps = computed(() =>
|
||||
filterWidgetProps(props.widget.options, STANDARD_EXCLUDED_PROPS)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-toggleswitch .p-toggleswitch-slider) {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
:deep(.p-toggleswitch:hover .p-toggleswitch-slider) {
|
||||
border-color: currentColor;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,19 +11,19 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex h-[30px] min-w-105 items-center justify-between gap-2 overscroll-contain contain-size"
|
||||
>
|
||||
<div class="relative flex h-6 min-w-28 shrink-1 items-center">
|
||||
<div class="flex h-[30px] min-w-78 items-center justify-between gap-1">
|
||||
<div
|
||||
class="relative flex h-full basis-content min-w-20 flex-1 items-center"
|
||||
>
|
||||
<p
|
||||
v-if="widget.name"
|
||||
class="lod-toggle flex-1 truncate text-sm font-normal text-node-component-slot-text"
|
||||
class="lod-toggle flex-1 truncate text-xs font-normal text-node-component-slot-text"
|
||||
>
|
||||
{{ widget.label || widget.name }}
|
||||
</p>
|
||||
<LODFallback />
|
||||
</div>
|
||||
<div class="relative min-w-75 grow-1">
|
||||
<div class="relative min-w-56 basis-full grow">
|
||||
<div
|
||||
class="lod-toggle cursor-default"
|
||||
@pointerdown.stop="noop"
|
||||
|
||||
@@ -6,9 +6,6 @@ export const WidgetInputBaseClass = cn([
|
||||
'not-disabled:text-component-node-foreground',
|
||||
// Outline
|
||||
'border-none',
|
||||
'outline outline-offset-[-1px] outline-component-node-border',
|
||||
// Rounded
|
||||
'rounded-lg',
|
||||
// Hover
|
||||
'hover:bg-component-node-widget-background-hovered'
|
||||
'rounded-lg'
|
||||
])
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const sharedButtonClasses =
|
||||
'!inline-flex !items-center !justify-center !border-0 bg-transparent text-inherit transition-colors duration-150 ease-in-out ' +
|
||||
'hover:bg-node-component-surface-hovered active:bg-node-component-surface-selected' +
|
||||
import { cn } from '@comfyorg/tailwind-utils'
|
||||
|
||||
const sharedButtonClasses = cn(
|
||||
'inline-flex items-center justify-center border-0 bg-transparent text-inherit transition-colors duration-150 ease-in-out ',
|
||||
'hover:bg-node-component-surface-hovered active:bg-node-component-surface-selected',
|
||||
'disabled:bg-node-component-disabled disabled:text-node-icon-disabled disabled:cursor-not-allowed'
|
||||
)
|
||||
|
||||
export function useNumberWidgetButtonPt(options?: {
|
||||
roundedLeft?: boolean
|
||||
@@ -9,15 +12,15 @@ export function useNumberWidgetButtonPt(options?: {
|
||||
}) {
|
||||
const { roundedLeft = false, roundedRight = false } = options ?? {}
|
||||
|
||||
const increment = `${sharedButtonClasses}${roundedRight ? ' !rounded-r-lg' : ''}`
|
||||
const decrement = `${sharedButtonClasses}${roundedLeft ? ' !rounded-l-lg' : ''}`
|
||||
const increment = cn(sharedButtonClasses, roundedRight && 'rounded-r-lg')
|
||||
const decrement = cn(sharedButtonClasses, roundedLeft && 'rounded-l-lg')
|
||||
|
||||
return {
|
||||
incrementButton: {
|
||||
class: increment.trim()
|
||||
class: increment
|
||||
},
|
||||
decrementButton: {
|
||||
class: decrement.trim()
|
||||
class: decrement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||