Compare commits

...

1 Commits

Author SHA1 Message Date
bymyself
436798d23d fix: designer QA polish for V2 Node Search (#8987)
- Category sidebar: nested expanded subfolders wrap in bg-secondary-background container
- Chevrons appear on sidebar hover for categories with children
- Filter chips: three visual states (active/applied/default) with applied count
- Preview card pricing badge truncation for overflow
- Pass appliedFilters prop to NodeSearchFilterBar
- Fix paddingLeft to preserve depth hierarchy when chevrons are visible
- Deduplicate button template in NodeSearchCategoryTreeNode
- Restore draftTypes.ts removed by merge conflict with #8993/#8519

Amp-Thread-ID: https://ampcode.com/threads/T-019c87f4-aa28-7290-bdf0-ea5f86aacde3
2026-02-22 16:54:34 -08:00
6 changed files with 111 additions and 41 deletions

View File

@@ -24,8 +24,8 @@
</p>
<!-- Badges -->
<div class="flex flex-wrap gap-2 empty:hidden">
<NodePricingBadge :node-def="nodeDef" />
<div class="flex flex-wrap gap-2 empty:hidden overflow-hidden">
<NodePricingBadge class="max-w-full truncate" :node-def="nodeDef" />
<NodeProviderBadge :node-def="nodeDef" />
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="flex min-h-0 flex-col overflow-y-auto py-2.5">
<div ref="sidebarRef" class="flex min-h-0 flex-col overflow-y-auto py-2.5">
<!-- Preset categories -->
<div class="flex flex-col px-1">
<button
@@ -38,6 +38,7 @@
:node="category"
:selected-category="selectedCategory"
:selected-collapsed="selectedCollapsed"
:show-chevrons="isHovered"
@select="selectCategory"
/>
</div>
@@ -45,6 +46,7 @@
</template>
<script setup lang="ts">
import { useElementHover } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -64,6 +66,9 @@ const selectedCategory = defineModel<string>('selectedCategory', {
required: true
})
const sidebarRef = ref<HTMLElement>()
const isHovered = useElementHover(sidebarRef)
const { t } = useI18n()
const { flags } = useFeatureFlags()
const nodeDefStore = useNodeDefStore()

View File

@@ -1,32 +1,47 @@
<template>
<button
type="button"
:data-testid="`category-${node.key}`"
:aria-current="selectedCategory === node.key || undefined"
:style="{ paddingLeft: `${0.75 + depth * 1.25}rem` }"
<div
:class="
cn(
'w-full cursor-pointer rounded border-none bg-transparent py-2.5 pr-3 text-left text-sm transition-colors',
selectedCategory === node.key
? CATEGORY_SELECTED_CLASS
: CATEGORY_UNSELECTED_CLASS
isExpanded &&
hasChildren &&
'flex flex-col rounded-lg bg-secondary-background'
)
"
@click="$emit('select', node.key)"
>
{{ node.label }}
</button>
<template v-if="isExpanded && node.children?.length">
<NodeSearchCategoryTreeNode
v-for="child in node.children"
:key="child.key"
:node="child"
:depth="depth + 1"
:selected-category="selectedCategory"
:selected-collapsed="selectedCollapsed"
@select="$emit('select', $event)"
/>
</template>
<Button
type="button"
variant="muted-textonly"
size="unset"
:data-testid="`category-${node.key}`"
:aria-current="selectedCategory === node.key || undefined"
:class="categoryButtonClass"
:style="{ paddingLeft: paddingLeft }"
@click="$emit('select', node.key)"
>
<i
v-if="showChevrons && hasChildren"
:class="
cn(
'pi pi-chevron-down shrink-0 text-[10px] transition-transform',
!isExpanded && '-rotate-90'
)
"
/>
{{ node.label }}
</Button>
<template v-if="isExpanded && hasChildren">
<NodeSearchCategoryTreeNode
v-for="child in node.children"
:key="child.key"
:node="child"
:depth="depth + 1"
:selected-category="selectedCategory"
:selected-collapsed="selectedCollapsed"
:show-chevrons="showChevrons"
@select="$emit('select', $event)"
/>
</template>
</div>
</template>
<script lang="ts">
@@ -45,26 +60,43 @@ export const CATEGORY_UNSELECTED_CLASS =
<script setup lang="ts">
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { cn } from '@/utils/tailwindUtil'
const {
node,
depth = 0,
selectedCategory,
selectedCollapsed = false
selectedCollapsed = false,
showChevrons = false
} = defineProps<{
node: CategoryNode
depth?: number
selectedCategory: string
selectedCollapsed?: boolean
showChevrons?: boolean
}>()
defineEmits<{
select: [key: string]
}>()
const hasChildren = computed(() => (node.children?.length ?? 0) > 0)
const isExpanded = computed(() => {
if (selectedCategory === node.key) return !selectedCollapsed
return selectedCategory.startsWith(node.key + '/')
})
const paddingLeft = computed(() => {
if (hasChildren.value && showChevrons) return `${0.5 + depth * 1.25}rem`
return `${0.75 + depth * 1.25}rem`
})
const categoryButtonClass = computed(() =>
cn(
'w-full justify-start rounded py-3 pr-3 font-normal hover:text-foreground',
selectedCategory === node.key && CATEGORY_SELECTED_CLASS
)
)
</script>

View File

@@ -25,6 +25,7 @@
<NodeSearchFilterBar
class="flex-1"
:active-chip-key="activeFilter?.key"
:applied-filters="filters"
@select-chip="onSelectFilterChip"
/>
</div>

View File

@@ -1,27 +1,28 @@
<template>
<div class="flex items-center gap-2 px-2 py-1.5">
<button
<Button
v-for="chip in chips"
:key="chip.key"
type="button"
:variant="chipVariant(chip.key)"
size="unset"
:aria-pressed="activeChipKey === chip.key"
:class="
cn(
'cursor-pointer rounded-md border px-3 py-1 text-sm transition-colors flex-auto border-secondary-background',
activeChipKey === chip.key
? 'bg-secondary-background text-foreground'
: 'bg-transparent text-muted-foreground hover:border-base-foreground/60 hover:text-base-foreground/60'
)
"
:class="chipExtraClass(chip.key)"
@click="emit('selectChip', chip)"
>
{{ chip.label }}
</button>
<span
v-if="appliedFilterCounts[chip.key]"
class="ml-0.5 text-xs opacity-80"
>
({{ appliedFilterCounts[chip.key] }})
</span>
</Button>
</div>
</template>
<script lang="ts">
import type { FuseFilter } from '@/utils/fuseUtil'
import type { FuseFilter, FuseFilterWithValue } from '@/utils/fuseUtil'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
export interface FilterChip {
@@ -35,11 +36,14 @@ export interface FilterChip {
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import type { ButtonVariants } from '@/components/ui/button/button.variants'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { cn } from '@/utils/tailwindUtil'
const { activeChipKey = null } = defineProps<{
const { activeChipKey = null, appliedFilters = [] } = defineProps<{
activeChipKey?: string | null
appliedFilters?: FuseFilterWithValue<ComfyNodeDefImpl, string>[]
}>()
const emit = defineEmits<{
@@ -69,4 +73,29 @@ const chips = computed<FilterChip[]>(() => {
}
]
})
const appliedFilterCounts = computed(() => {
const counts: Record<string, number> = {}
for (const filter of appliedFilters) {
counts[filter.filterDef.id] = (counts[filter.filterDef.id] ?? 0) + 1
}
return counts
})
function chipVariant(chipKey: string): ButtonVariants['variant'] {
return activeChipKey === chipKey ? 'inverted' : 'outline'
}
function chipExtraClass(chipKey: string) {
const isActive = activeChipKey === chipKey
const hasApplied = (appliedFilterCounts.value[chipKey] ?? 0) > 0
return cn(
'flex-auto px-3 py-1',
isActive && 'border border-solid border-base-foreground',
!isActive &&
hasApplied &&
'bg-base-foreground/10 border-base-foreground text-base-foreground'
)
}
</script>

View File

@@ -19,7 +19,9 @@ export const buttonVariants = cva({
'text-muted-foreground bg-transparent hover:bg-secondary-background-hover',
'destructive-textonly':
'text-destructive-background bg-transparent hover:bg-destructive-background/10',
'overlay-white': 'bg-white text-gray-600 hover:bg-white/90'
'overlay-white': 'bg-white text-gray-600 hover:bg-white/90',
outline:
'border border-solid border-muted-foreground bg-transparent text-muted-foreground hover:border-base-foreground/60 hover:text-base-foreground/60'
},
size: {
sm: 'h-6 rounded-sm px-2 py-1 text-xs',
@@ -47,7 +49,8 @@ const variants = [
'textonly',
'muted-textonly',
'destructive-textonly',
'overlay-white'
'overlay-white',
'outline'
] as const satisfies Array<ButtonVariants['variant']>
const sizes = ['sm', 'md', 'lg', 'icon', 'icon-sm'] as const satisfies Array<
ButtonVariants['size']