fix(web): style of tag filter of snippets

This commit is contained in:
JzoNg
2026-05-27 22:00:05 +08:00
parent 1cf6cdb764
commit 056caa8b2f
4 changed files with 112 additions and 71 deletions

View File

@@ -27,9 +27,6 @@ import {
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
Plus02,
} from '@/app/components/base/icons/src/vender/line/general'
import Input from '@/app/components/base/input'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
@@ -163,7 +160,7 @@ function NodeSelector({
if (onOpenChange)
onOpenChange(newOpen)
}, [disabled, onOpenChange])
}, [activeTab, disabled, onOpenChange])
const handleTrigger = useCallback<MouseEventHandler<HTMLElement>>((e) => {
e.stopPropagation()
}, [])
@@ -219,7 +216,7 @@ function NodeSelector({
style={triggerStyle}
onClick={handleTrigger}
>
<Plus02 aria-hidden className="size-2.5" />
<span aria-hidden className="i-custom-vender-line-general-plus-02 size-2.5" />
</PopoverTrigger>
)
const triggerElement = trigger?.(open)
@@ -264,65 +261,56 @@ function NodeSelector({
blocks={blocks}
allowStartNodeSelection={canSelectUserInput}
onActiveTabChange={handleActiveTabChange}
filterElem={(
<div className="relative m-2" onClick={e => e.stopPropagation()}>
{activeTab === TabsEnum.Start && (
<SearchBox
autoFocus
search={searchText}
onSearchChange={setSearchText}
tags={tags}
onTagsChange={setTags}
placeholder={searchPlaceholder}
inputClassName="grow"
/>
filterElem={activeTab === TabsEnum.Snippets
? null
: (
<div className="relative m-2" onClick={e => e.stopPropagation()}>
{activeTab === TabsEnum.Start && (
<SearchBox
autoFocus
search={searchText}
onSearchChange={setSearchText}
tags={tags}
onTagsChange={setTags}
placeholder={searchPlaceholder}
inputClassName="grow"
/>
)}
{activeTab === TabsEnum.Blocks && (
<Input
showLeftIcon
showClearIcon
autoFocus
value={searchText}
placeholder={searchPlaceholder}
onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')}
/>
)}
{activeTab === TabsEnum.Sources && (
<Input
showLeftIcon
showClearIcon
autoFocus
value={searchText}
placeholder={searchPlaceholder}
onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')}
/>
)}
{activeTab === TabsEnum.Tools && (
<SearchBox
autoFocus
search={searchText}
onSearchChange={setSearchText}
tags={tags}
onTagsChange={setTags}
placeholder={t('searchTools', { ns: 'plugin' })!}
inputClassName="grow"
/>
)}
</div>
)}
{activeTab === TabsEnum.Blocks && (
<Input
showLeftIcon
showClearIcon
autoFocus
value={searchText}
placeholder={searchPlaceholder}
onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')}
/>
)}
{activeTab === TabsEnum.Sources && (
<Input
showLeftIcon
showClearIcon
autoFocus
value={searchText}
placeholder={searchPlaceholder}
onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')}
/>
)}
{activeTab === TabsEnum.Tools && (
<SearchBox
autoFocus
search={searchText}
onSearchChange={setSearchText}
tags={tags}
onTagsChange={setTags}
placeholder={t('searchTools', { ns: 'plugin' })!}
inputClassName="grow"
/>
)}
{activeTab === TabsEnum.Snippets && (
<Input
showLeftIcon
showClearIcon
autoFocus
value={searchText}
placeholder={searchPlaceholder}
onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')}
/>
)}
</div>
)}
onSelect={handleSelect}
searchText={searchText}
tags={tags}
@@ -338,6 +326,7 @@ function NodeSelector({
<Snippets
loading={snippetsLoading}
searchText={searchText}
onSearchTextChange={setSearchText}
insertPayload={snippetInsertPayload}
onInserted={() => handleOpenChange(false)}
/>

View File

@@ -119,6 +119,18 @@ describe('Snippets', () => {
})
})
it('should delegate embedded search changes', () => {
const onSearchTextChange = vi.fn()
render(<Snippets searchText="" onSearchTextChange={onSearchTextChange} />)
fireEvent.change(screen.getByPlaceholderText('workflow.tabs.searchSnippets'), {
target: { value: 'review' },
})
expect(onSearchTextChange).toHaveBeenCalledWith('review')
})
it('should delegate insert action when snippet item is clicked', () => {
mockUseInfiniteSnippetList.mockReturnValue({
data: {

View File

@@ -21,6 +21,7 @@ import {
useRef,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
import { useInfiniteSnippetList } from '@/service/use-snippets'
import SnippetDetailCard from './snippet-detail-card'
@@ -32,6 +33,7 @@ import { useInsertSnippet } from './use-insert-snippet'
type SnippetsProps = {
loading?: boolean
searchText: string
onSearchTextChange?: (searchText: string) => void
insertPayload?: Parameters<OnNodeAdd>[1]
onInserted?: () => void
}
@@ -62,9 +64,11 @@ const LoadingSkeleton = () => {
const Snippets = ({
loading = false,
searchText,
onSearchTextChange,
insertPayload,
onInserted,
}: SnippetsProps) => {
const { t } = useTranslation()
const { handleInsertSnippet } = useInsertSnippet()
const deferredSearchText = useDeferredValue(searchText)
const viewportRef = useRef<HTMLDivElement>(null)
@@ -114,16 +118,42 @@ const Snippets = ({
},
)
const tagsFilter = (
<div className="flex justify-end border-b border-divider-subtle px-2 py-2">
<SnippetTagsFilter value={tagIds} onChange={setTagIds} />
const filter = (
<div className="border-b border-divider-subtle p-2">
<div className="flex items-center rounded-lg border border-transparent bg-components-input-bg-normal focus-within:border-components-input-border-active hover:border-components-input-border-hover">
<div className="flex min-w-0 grow items-center py-1.75 pr-3 pl-2">
<span className="i-ri-search-line size-4 shrink-0 text-components-input-text-placeholder" aria-hidden="true" />
<input
autoFocus
value={searchText}
placeholder={t('tabs.searchSnippets', { ns: 'workflow' })}
className={cn(
'mr-1 ml-1.5 inline-block min-w-0 grow appearance-none bg-transparent system-sm-regular text-components-input-text-filled outline-hidden placeholder:text-components-input-text-placeholder',
searchText && 'mr-2',
)}
onChange={event => onSearchTextChange?.(event.target.value)}
/>
{!!searchText && (
<button
type="button"
aria-label={t('operation.clear', { ns: 'common' })}
className="group shrink-0 cursor-pointer rounded-md p-1 hover:bg-state-base-hover"
onClick={() => onSearchTextChange?.('')}
>
<span className="i-ri-close-line size-4 text-text-tertiary" aria-hidden="true" />
</button>
)}
</div>
<div className="mx-0 mr-0.5 h-3.5 w-px bg-divider-regular" />
<SnippetTagsFilter embedded value={tagIds} onChange={setTagIds} />
</div>
</div>
)
if (loading || isLoading || (isFetching && snippets.length === 0)) {
return (
<>
{tagsFilter}
{filter}
<LoadingSkeleton />
</>
)
@@ -131,7 +161,7 @@ const Snippets = ({
return (
<>
{tagsFilter}
{filter}
{!snippets.length
? (
<SnippetEmptyState />
@@ -161,7 +191,7 @@ const Snippets = ({
render={row}
/>
<TooltipContent
placement="left-start"
placement="right-start"
className="bg-transparent! p-0!"
>
<SnippetDetailCard snippet={item} />

View File

@@ -14,11 +14,13 @@ import Tag01Icon from '@/app/components/base/icons/src/vender/line/financeAndECo
import { consoleQuery } from '@/service/client'
type SnippetTagsFilterProps = {
embedded?: boolean
value: string[]
onChange: (value: string[]) => void
}
const SnippetTagsFilter = ({
embedded = false,
value,
onChange,
}: SnippetTagsFilterProps) => {
@@ -59,8 +61,16 @@ const SnippetTagsFilter = ({
type="button"
aria-label={triggerLabel}
className={cn(
'relative flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-lg border-[0.5px] border-components-panel-border bg-components-input-bg-normal px-2 text-text-tertiary hover:bg-components-input-bg-hover',
open && 'border-components-input-border-active bg-components-input-bg-active text-text-secondary',
'relative flex cursor-pointer items-center justify-center text-text-tertiary select-none',
embedded
? 'h-7 rounded-md p-0.5'
: 'h-8 min-w-8 rounded-lg border-[0.5px] border-components-panel-border bg-components-input-bg-normal px-2',
embedded && !value.length && 'py-1 pr-2 pl-1.5',
embedded && value.length > 0 && 'border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg py-0.5 pr-1.5 pl-1 shadow-xs shadow-shadow-shadow-3',
!embedded && 'hover:bg-components-input-bg-hover',
open && (embedded
? !value.length && 'bg-state-base-hover'
: 'border-components-input-border-active bg-components-input-bg-active text-text-secondary'),
value.length > 0 && 'text-text-secondary',
)}
>