mirror of
https://github.com/langgenius/dify.git
synced 2026-05-31 19:00:22 -04:00
fix(web): style of tag filter of snippets
This commit is contained in:
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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',
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user