mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-25 16:59:45 +00:00
feat: update NumberControlPopover with semantic design tokens
This commit is contained in:
@@ -87,11 +87,16 @@ const handleEditSettings = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover ref="popover">
|
||||
<div class="w-105 p-4 space-y-4">
|
||||
<p class="text-sm text-slate-100">
|
||||
<Popover
|
||||
ref="popover"
|
||||
class="bg-interface-panel-surface border border-interface-stroke rounded-lg"
|
||||
>
|
||||
<!-- Responsive width with proper constraints -->
|
||||
<div class="w-113 max-w-md p-4 space-y-4">
|
||||
<!-- Header text with semantic tokens -->
|
||||
<div class="text-sm text-muted-foreground leading-tight">
|
||||
{{ $t('widgets.numberControl.controlHeaderBefore') }}
|
||||
<span class="text-white">
|
||||
<span class="text-base-foreground font-medium">
|
||||
{{
|
||||
widgetControlMode === 'before'
|
||||
? $t('widgets.numberControl.controlHeaderBefore2')
|
||||
@@ -99,25 +104,38 @@ const handleEditSettings = () => {
|
||||
}}
|
||||
</span>
|
||||
{{ $t('widgets.numberControl.controlHeaderEnd') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Control options with proper spacing -->
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="option in controlOptions"
|
||||
:key="option.mode"
|
||||
class="flex items-center justify-between p-2 rounded"
|
||||
class="flex items-center justify-between py-2 gap-7"
|
||||
>
|
||||
<div class="flex gap-3 flex-1">
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||
<!-- Icon container with semantic background -->
|
||||
<div
|
||||
class="w-8 h-8 bg-charcoal-400 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||
class="flex items-center justify-center w-8 h-8 rounded-lg flex-shrink-0 bg-secondary-background border border-border-subtle"
|
||||
>
|
||||
<i v-if="option.icon" :class="`${option.icon} text-sm`" />
|
||||
<span v-if="option.text" class="text-xs">
|
||||
<i
|
||||
v-if="option.icon"
|
||||
:class="option.icon"
|
||||
class="text-base text-base-foreground"
|
||||
/>
|
||||
<span
|
||||
v-if="option.text"
|
||||
class="text-xs font-normal text-base-foreground"
|
||||
>
|
||||
{{ option.text }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-sm font-normal">
|
||||
|
||||
<!-- Text content with proper semantic colors -->
|
||||
<div class="flex flex-col gap-0.5 min-w-0 flex-1">
|
||||
<div
|
||||
class="text-sm font-normal text-base-foreground leading-tight"
|
||||
>
|
||||
<span v-if="option.mode === NumberControlMode.LINK_TO_GLOBAL">
|
||||
{{ $t('widgets.numberControl.linkToGlobal') }}
|
||||
<em>{{ $t('widgets.numberControl.linkToGlobalSeed') }}</em>
|
||||
@@ -126,11 +144,15 @@ const handleEditSettings = () => {
|
||||
{{ $t(`widgets.numberControl.${option.title}`) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm font-normal text-slate-100">
|
||||
<div
|
||||
class="text-sm font-normal text-muted-foreground leading-tight"
|
||||
>
|
||||
{{ $t(`widgets.numberControl.${option.description}`) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toggle switch with proper sizing -->
|
||||
<ToggleSwitch
|
||||
:model-value="isActive(option.mode)"
|
||||
class="flex-shrink-0"
|
||||
@@ -139,16 +161,20 @@ const handleEditSettings = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-charcoal-400 border-1" />
|
||||
<!-- Divider using semantic border -->
|
||||
<div class="border-t border-border-subtle"></div>
|
||||
|
||||
<!-- Settings button with semantic styling -->
|
||||
<Button
|
||||
severity="secondary"
|
||||
size="small"
|
||||
class="w-full"
|
||||
class="w-full bg-secondary-background hover:bg-secondary-background-hover border-0 rounded-lg p-2 text-sm"
|
||||
@click="handleEditSettings"
|
||||
>
|
||||
<i class="pi pi-cog mr-2 text-xs" />
|
||||
{{ $t('widgets.numberControl.editSettings') }}
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<i class="pi pi-cog text-xs text-muted-foreground" />
|
||||
<span class="font-normal text-base-foreground">{{
|
||||
$t('widgets.numberControl.editSettings')
|
||||
}}</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -63,7 +63,7 @@ const togglePopover = (event: Event) => {
|
||||
<Button
|
||||
variant="link"
|
||||
size="small"
|
||||
class="absolute right-12 top-1/2 -translate-y-1/2 h-4 w-7 p-0 bg-blue-100/30 rounded-xl"
|
||||
class="absolute top-1/2 right-12 h-4 w-7 -translate-y-1/2 rounded-xl bg-blue-100/30 p-0"
|
||||
@click="togglePopover"
|
||||
>
|
||||
<i :class="`${controlButtonIcon} text-blue-100 text-xs`" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { type Ref, computed, defineAsyncComponent, ref } from 'vue'
|
||||
import { computed, defineAsyncComponent, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
@@ -89,7 +90,7 @@ const setControlMode = (mode: NumberControlMode) => {
|
||||
<Button
|
||||
variant="link"
|
||||
size="small"
|
||||
class="absolute right-12 top-1/2 -translate-y-1/2 h-4 w-7 p-0 bg-blue-100/30 rounded-xl"
|
||||
class="absolute top-1/2 right-12 h-4 w-7 -translate-y-1/2 rounded-xl bg-blue-100/30 p-0"
|
||||
@click="togglePopover"
|
||||
>
|
||||
<i :class="`${controlButtonIcon} text-blue-100 text-xs`" />
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { ResultItemType } from '@/schemas/apiSchema'
|
||||
import {
|
||||
type ComboInputSpec,
|
||||
isComboInputSpec
|
||||
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { ComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { isComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import type { AssetKind } from '@/types/widgetTypes'
|
||||
|
||||
|
||||
@@ -150,6 +150,10 @@ const outputItems = computed<DropdownItem[]>(() => {
|
||||
}))
|
||||
})
|
||||
|
||||
const allItems = computed<DropdownItem[]>(() => {
|
||||
return [...inputItems.value, ...outputItems.value]
|
||||
})
|
||||
|
||||
const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
if (props.isAssetMode) {
|
||||
return allItems.value
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ComputedRef, type Ref, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
|
||||
import { numberControlRegistry } from '../services/NumberControlRegistry'
|
||||
import { NumberControlMode } from './useStepperControl'
|
||||
|
||||
@@ -13,16 +13,16 @@ import {
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
||||
import { isComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type {
|
||||
ComboInputSpec,
|
||||
InputSpec
|
||||
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { isComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
||||
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
|
||||
import type { BaseDOMWidget } from '@/scripts/domWidget'
|
||||
import { addValueControlWidgets } from '@/scripts/widgets'
|
||||
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||
import { addValueControlWidget, addValueControlWidgets } from '@/scripts/widgets'
|
||||
import { useAssetsStore } from '@/stores/assetsStore'
|
||||
import { getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
|
||||
@@ -69,6 +69,16 @@ const addMultiSelectWidget = (
|
||||
addWidget(node, widget as BaseDOMWidget<object | string>)
|
||||
// TODO: Add remote support to multi-select widget
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/3003
|
||||
if (inputSpec.control_after_generate) {
|
||||
widget.linkedWidgets = addValueControlWidgets(
|
||||
node,
|
||||
widget,
|
||||
undefined,
|
||||
undefined,
|
||||
transformInputSpecV2ToV1(inputSpec)
|
||||
)
|
||||
}
|
||||
|
||||
return widget
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Ref, computed } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { NumberControlMode } from './useStepperControl'
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { isFloatInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||
import { addValueControlWidget } from '@/scripts/widgets'
|
||||
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
||||
|
||||
function onFloatValueChange(this: INumericWidget, v: number) {
|
||||
const round = this.options.round
|
||||
@@ -55,7 +57,7 @@ export const useFloatWidget = () => {
|
||||
|
||||
/** Assertion {@link inputSpec.default} */
|
||||
const defaultValue = (inputSpec.default as number | undefined) ?? 0
|
||||
return node.addWidget(
|
||||
const widget = node.addWidget(
|
||||
widgetType,
|
||||
inputSpec.name,
|
||||
defaultValue,
|
||||
@@ -73,6 +75,20 @@ export const useFloatWidget = () => {
|
||||
precision
|
||||
}
|
||||
)
|
||||
|
||||
if (inputSpec.control_after_generate) {
|
||||
const controlWidget = addValueControlWidget(
|
||||
node,
|
||||
widget,
|
||||
'randomize',
|
||||
undefined,
|
||||
undefined,
|
||||
transformInputSpecV2ToV1(inputSpec)
|
||||
)
|
||||
widget.linkedWidgets = [controlWidget]
|
||||
}
|
||||
|
||||
return widget
|
||||
}
|
||||
|
||||
return widgetConstructor
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
||||
import { isIntInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { addValueControlWidget } from '@/scripts/widgets'
|
||||
import { isIntInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||
import { addValueControlWidget } from '@/scripts/widgets'
|
||||
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
||||
|
||||
function onValueChange(this: INumericWidget, v: number) {
|
||||
// For integers, always round to the nearest step
|
||||
@@ -69,14 +69,10 @@ export const useIntWidget = () => {
|
||||
|
||||
const controlAfterGenerate =
|
||||
inputSpec.control_after_generate ??
|
||||
/**
|
||||
* Compatibility with legacy node convention. Int input with name
|
||||
* 'seed' or 'noise_seed' get automatically added a control widget.
|
||||
*/
|
||||
['seed', 'noise_seed'].includes(inputSpec.name)
|
||||
|
||||
if (controlAfterGenerate) {
|
||||
const seedControl = addValueControlWidget(
|
||||
const controlWidget = addValueControlWidget(
|
||||
node,
|
||||
widget,
|
||||
'randomize',
|
||||
@@ -84,7 +80,7 @@ export const useIntWidget = () => {
|
||||
undefined,
|
||||
transformInputSpecV2ToV1(inputSpec)
|
||||
)
|
||||
widget.linkedWidgets = [seedControl]
|
||||
widget.linkedWidgets = [controlWidget]
|
||||
}
|
||||
|
||||
return widget
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Ref, computed } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
interface NumberWidgetOptions {
|
||||
step2?: number
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Ref, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useGlobalSeedStore } from '@/stores/globalSeedStore'
|
||||
|
||||
|
||||
@@ -72,7 +72,11 @@ const coreWidgetDefinitions: Array<[string, WidgetDefinition]> = [
|
||||
],
|
||||
[
|
||||
'multiselect',
|
||||
{ component: WidgetMultiSelect, aliases: ['MULTISELECT'], essential: false }
|
||||
{
|
||||
component: WidgetMultiSelect,
|
||||
aliases: ['MULTISELECT'],
|
||||
essential: false
|
||||
}
|
||||
],
|
||||
[
|
||||
'selectbutton',
|
||||
@@ -113,7 +117,11 @@ const coreWidgetDefinitions: Array<[string, WidgetDefinition]> = [
|
||||
],
|
||||
[
|
||||
'treeselect',
|
||||
{ component: WidgetTreeSelect, aliases: ['TREESELECT'], essential: false }
|
||||
{
|
||||
component: WidgetTreeSelect,
|
||||
aliases: ['TREESELECT'],
|
||||
essential: false
|
||||
}
|
||||
],
|
||||
[
|
||||
'markdown',
|
||||
|
||||
Reference in New Issue
Block a user