Files
ComfyUI_frontend/src/components/input/SearchBox.vue
Johnpaul Chiwetelu c56fff0b8b Workflow templates review (#5975)
This pull request introduces improvements to the workflow template
selector and search box components, focusing on better user experience
and more accurate terminology. The most significant changes include
adding debounced search input handling, updating sorting option labels,
and refining UI styling for consistency.

**Search functionality improvements:**
* Refactored `SearchBox.vue` to use an internal search query state and a
debounced update mechanism, reducing unnecessary parent updates and
improving responsiveness. The parent model is updated only after the
user stops typing for 300ms. (`src/components/input/SearchBox.vue`)
[[1]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bL6-R6)
[[2]](diffhunk://#diff-08f3b0c51fbfe63171509b9944bf7558228f6c2596a1ef5338e88ab64585791bR39-R62)
* Updated the search box in `WorkflowTemplateSelectorDialog.vue` to use
the new debounced search model and increased its size for better
visibility.
(`src/components/custom/widget/WorkflowTemplateSelectorDialog.vue`)

**Sorting and terminology updates:**
* Changed sorting option labels to use more precise terminology, such as
"VRAM Usage (Low to High)" and added new locale strings for sorting
options.
(`src/components/custom/widget/WorkflowTemplateSelectorDialog.vue`,
`src/locales/en/main.json`)
[[1]](diffhunk://#diff-2c860bdc48e907b1b85dbef846599d8376dd02cff90f49e490eebe61371fecedL623-R623)
[[2]](diffhunk://#diff-bbf3da78aeff5b4d868a17a6960d109cb0627316cda2f9b5fa7c08e9abd93be6L1032-R1035)

**UI and styling adjustments:**
* Adjusted the width of the sorting dropdown for better alignment and
consistency.
(`src/components/custom/widget/WorkflowTemplateSelectorDialog.vue`)
* Updated active navigation item background color for improved visual
clarity. (`src/components/widget/nav/NavItem.vue`)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5975-Workflow-templates-review-2866d73d365081419257f9df2bab9c5b)
by [Unito](https://www.unito.io)



https://github.com/user-attachments/assets/4f72d515-f114-4cd4-8a76-6abbe906e5bb
2025-10-08 14:06:03 -07:00

113 lines
2.8 KiB
Vue

<template>
<div :class="wrapperStyle" @click="focusInput">
<i class="icon-[lucide--search]" :class="iconColorStyle" />
<InputText
ref="input"
v-model="internalSearchQuery"
:aria-label="
placeholder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
"
:placeholder="
placeholder || t('templateWidgets.sort.searchPlaceholder', 'Search...')
"
type="text"
unstyled
:class="inputStyle"
/>
</div>
</template>
<script setup lang="ts">
import { useDebounceFn } from '@vueuse/core'
import InputText from 'primevue/inputtext'
import { computed, onMounted, ref, watch } from 'vue'
import { t } from '@/i18n'
import { cn } from '@/utils/tailwindUtil'
const SEARCH_DEBOUNCE_DELAY_MS = 300
const {
autofocus = false,
placeholder,
showBorder = false,
size = 'md'
} = defineProps<{
autofocus?: boolean
placeholder?: string
showBorder?: boolean
size?: 'md' | 'lg'
}>()
// defineModel without arguments uses 'modelValue' as the prop name
const searchQuery = defineModel<string>()
// Internal search query state for immediate UI updates
const internalSearchQuery = ref<string>(searchQuery.value ?? '')
// Create debounced function to update the parent model
const updateSearchQuery = useDebounceFn((value: string) => {
searchQuery.value = value
}, SEARCH_DEBOUNCE_DELAY_MS)
// Watch internal query changes and trigger debounced update
watch(internalSearchQuery, (newValue) => {
void updateSearchQuery(newValue)
})
// Sync external changes back to internal state
watch(searchQuery, (newValue) => {
if (newValue !== internalSearchQuery.value) {
internalSearchQuery.value = newValue || ''
}
})
const input = ref<{ $el: HTMLElement } | null>()
const focusInput = () => {
if (input.value && input.value.$el) {
input.value.$el.focus()
}
}
onMounted(() => autofocus && focusInput())
const wrapperStyle = computed(() => {
const baseClasses = [
'relative flex w-full items-center gap-2',
'bg-white dark-theme:bg-zinc-800',
'cursor-text'
]
if (showBorder) {
return cn(
...baseClasses,
'rounded p-2',
'border border-solid',
'border-zinc-200 dark-theme:border-zinc-700'
)
}
// Size-specific classes matching button sizes for consistency
const sizeClasses = {
md: 'h-8 px-2 py-1.5', // Matches button sm size
lg: 'h-10 px-4 py-2' // Matches button md size
}[size]
return cn(...baseClasses, 'rounded-lg', sizeClasses)
})
const inputStyle = computed(() => {
return cn(
'absolute inset-0 w-full h-full pl-11',
'border-none outline-none bg-transparent',
'text-sm text-neutral dark-theme:text-white'
)
})
const iconColorStyle = computed(() => {
return cn(
!showBorder ? 'text-neutral' : ['text-zinc-300', 'dark-theme:text-zinc-700']
)
})
</script>