From f73be8d69e93dae3c3603e7f176e5aa830a0da32 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Fri, 27 Feb 2026 20:42:30 +0800 Subject: [PATCH] feat(web): add hover clear button for provider search (#32707) Signed-off-by: -LAN- Co-authored-by: yyh Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> --- web/app/components/base/input/index.spec.tsx | 10 +-- web/app/components/base/input/index.tsx | 13 ++-- .../file-list/__tests__/index.spec.tsx | 8 +-- .../file-list/header/__tests__/index.spec.tsx | 23 +++---- .../header/account-setting/index.tsx | 68 +++++++------------ web/eslint-suppressions.json | 6 -- 6 files changed, 51 insertions(+), 77 deletions(-) diff --git a/web/app/components/base/input/index.spec.tsx b/web/app/components/base/input/index.spec.tsx index 65589ddcdf..0aaaf51af5 100644 --- a/web/app/components/base/input/index.spec.tsx +++ b/web/app/components/base/input/index.spec.tsx @@ -43,7 +43,7 @@ describe('Input component', () => { it('shows left icon when showLeftIcon is true', () => { render() - const searchIcon = document.querySelector('svg') + const searchIcon = document.querySelector('.i-ri-search-line') expect(searchIcon).toBeInTheDocument() const input = screen.getByPlaceholderText('Search') expect(input).toHaveClass('pl-[26px]') @@ -51,7 +51,7 @@ describe('Input component', () => { it('shows clear icon when showClearIcon is true and has value', () => { render() - const clearIcon = document.querySelector('.group svg') + const clearIcon = document.querySelector('.i-ri-close-circle-fill') expect(clearIcon).toBeInTheDocument() const input = screen.getByDisplayValue('test') expect(input).toHaveClass('pr-[26px]') @@ -59,21 +59,21 @@ describe('Input component', () => { it('does not show clear icon when disabled, even with value', () => { render() - const clearIcon = document.querySelector('.group svg') + const clearIcon = document.querySelector('.i-ri-close-circle-fill') expect(clearIcon).not.toBeInTheDocument() }) it('calls onClear when clear icon is clicked', () => { const onClear = vi.fn() render() - const clearIconContainer = document.querySelector('.group') + const clearIconContainer = screen.getByTestId('input-clear') fireEvent.click(clearIconContainer!) expect(onClear).toHaveBeenCalledTimes(1) }) it('shows warning icon when destructive is true', () => { render() - const warningIcon = document.querySelector('svg') + const warningIcon = document.querySelector('.i-ri-error-warning-line') expect(warningIcon).toBeInTheDocument() const input = screen.getByPlaceholderText('Please input') expect(input).toHaveClass('border-components-input-border-destructive') diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx index ae76b71a1c..4ab88e80ce 100644 --- a/web/app/components/base/input/index.tsx +++ b/web/app/components/base/input/index.tsx @@ -1,6 +1,5 @@ import type { VariantProps } from 'class-variance-authority' import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react' -import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react' import { cva } from 'class-variance-authority' import { noop } from 'es-toolkit/function' import * as React from 'react' @@ -13,8 +12,8 @@ export const inputVariants = cva( { variants: { size: { - regular: 'px-3 radius-md system-sm-regular', - large: 'px-4 radius-lg system-md-regular', + regular: 'px-3 system-sm-regular radius-md', + large: 'px-4 system-md-regular radius-lg', }, }, defaultVariants: { @@ -83,7 +82,7 @@ const Input = React.forwardRef(({ } return (
- {showLeftIcon && } + {showLeftIcon && } (({ onClick={onClear} data-testid="input-clear" > - +
)} {destructive && ( - + )} {showCopyIcon && (
@@ -131,7 +130,7 @@ const Input = React.forwardRef(({ )} { unit && ( -
+
{unit}
) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/__tests__/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/__tests__/index.spec.tsx index c441709ec2..b0cbedd428 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/__tests__/index.spec.tsx @@ -334,10 +334,10 @@ describe('FileList', () => { it('should call resetKeywords prop when clear button is clicked', () => { const mockResetKeywords = vi.fn() const props = createDefaultProps({ resetKeywords: mockResetKeywords, keywords: 'to-reset' }) - const { container } = render() + render() // Act - Click the clear icon div (it contains RiCloseCircleFill icon) - const clearButton = container.querySelector('[class*="cursor-pointer"] svg[class*="h-3.5"]')?.parentElement + const clearButton = screen.getByTestId('input-clear') expect(clearButton).toBeInTheDocument() fireEvent.click(clearButton!) @@ -346,12 +346,12 @@ describe('FileList', () => { it('should reset inputValue to empty string when clear is clicked', () => { const props = createDefaultProps({ keywords: 'to-be-reset' }) - const { container } = render() + render() const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder') fireEvent.change(input, { target: { value: 'some-search' } }) // Act - Find and click the clear icon - const clearButton = container.querySelector('[class*="cursor-pointer"] svg[class*="h-3.5"]')?.parentElement + const clearButton = screen.getByTestId('input-clear') expect(clearButton).toBeInTheDocument() fireEvent.click(clearButton!) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/__tests__/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/__tests__/index.spec.tsx index ef94fd3dc8..07308361ad 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/__tests__/index.spec.tsx @@ -93,8 +93,8 @@ describe('Header', () => { const { container } = render(
) - // Assert - Input should have search icon (RiSearchLine is rendered as svg) - const searchIcon = container.querySelector('svg.h-4.w-4') + // Assert - Input should have search icon class + const searchIcon = container.querySelector('.i-ri-search-line.h-4.w-4') expect(searchIcon).toBeInTheDocument() }) @@ -313,10 +313,10 @@ describe('Header', () => { inputValue: 'to-clear', handleResetKeywords: mockHandleResetKeywords, }) - const { container } = render(
) + render(
) // Act - Find and click the clear icon container - const clearButton = container.querySelector('[class*="cursor-pointer"] svg[class*="h-3.5"]')?.parentElement + const clearButton = screen.getByTestId('input-clear') expect(clearButton).toBeInTheDocument() fireEvent.click(clearButton!) @@ -325,19 +325,19 @@ describe('Header', () => { it('should not show clear icon when inputValue is empty', () => { const props = createDefaultProps({ inputValue: '' }) - const { container } = render(
) + render(
) // Act & Assert - Clear icon should not be visible - const clearIcon = container.querySelector('[class*="cursor-pointer"] svg[class*="h-3.5"]') + const clearIcon = screen.queryByTestId('input-clear') expect(clearIcon).not.toBeInTheDocument() }) it('should show clear icon when inputValue is not empty', () => { const props = createDefaultProps({ inputValue: 'some-value' }) - const { container } = render(
) + render(
) // Act & Assert - Clear icon should be visible - const clearIcon = container.querySelector('[class*="cursor-pointer"] svg[class*="h-3.5"]') + const clearIcon = screen.getByTestId('input-clear') expect(clearIcon).toBeInTheDocument() }) }) @@ -570,13 +570,12 @@ describe('Header', () => { inputValue: 'to-clear', handleResetKeywords: mockHandleResetKeywords, }) - const { container, rerender } = render(
) + const { rerender } = render(
) // Act - Click clear, rerender, click again - const clearButton = container.querySelector('[class*="cursor-pointer"] svg[class*="h-3.5"]')?.parentElement - fireEvent.click(clearButton!) + fireEvent.click(screen.getByTestId('input-clear')) rerender(
) - fireEvent.click(clearButton!) + fireEvent.click(screen.getByTestId('input-clear')) expect(mockHandleResetKeywords).toHaveBeenCalledTimes(2) }) diff --git a/web/app/components/header/account-setting/index.tsx b/web/app/components/header/account-setting/index.tsx index 5de543c01b..65ac396529 100644 --- a/web/app/components/header/account-setting/index.tsx +++ b/web/app/components/header/account-setting/index.tsx @@ -1,24 +1,8 @@ 'use client' import type { AccountSettingTab } from '@/app/components/header/account-setting/constants' -import { - RiBrain2Fill, - RiBrain2Line, - RiCloseLine, - RiColorFilterFill, - RiColorFilterLine, - RiDatabase2Fill, - RiDatabase2Line, - RiGroup2Fill, - RiGroup2Line, - RiMoneyDollarCircleFill, - RiMoneyDollarCircleLine, - RiPuzzle2Fill, - RiPuzzle2Line, - RiTranslate2, -} from '@remixicon/react' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Input from '@/app/components/base/input' +import SearchInput from '@/app/components/base/search-input' import BillingPage from '@/app/components/billing/billing-page' import CustomPage from '@/app/components/custom/custom-page' import { @@ -76,14 +60,14 @@ export default function AccountSetting({ { key: ACCOUNT_SETTING_TAB.PROVIDER, name: t('settings.provider', { ns: 'common' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }, { key: ACCOUNT_SETTING_TAB.MEMBERS, name: t('settings.members', { ns: 'common' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }, ] @@ -92,8 +76,8 @@ export default function AccountSetting({ key: ACCOUNT_SETTING_TAB.BILLING, name: t('settings.billing', { ns: 'common' }), description: t('plansCommon.receiptInfo', { ns: 'billing' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }) } @@ -101,14 +85,14 @@ export default function AccountSetting({ { key: ACCOUNT_SETTING_TAB.DATA_SOURCE, name: t('settings.dataSource', { ns: 'common' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }, { key: ACCOUNT_SETTING_TAB.API_BASED_EXTENSION, name: t('settings.apiBasedExtension', { ns: 'common' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }, ) @@ -116,8 +100,8 @@ export default function AccountSetting({ items.push({ key: ACCOUNT_SETTING_TAB.CUSTOM, name: t('custom', { ns: 'custom' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }) } @@ -140,8 +124,8 @@ export default function AccountSetting({ { key: ACCOUNT_SETTING_TAB.LANGUAGE, name: t('settings.language', { ns: 'common' }), - icon: , - activeIcon: , + icon: , + activeIcon: , }, ], }, @@ -171,13 +155,13 @@ export default function AccountSetting({ >
-
{t('userProfile.settings', { ns: 'common' })}
+
{t('userProfile.settings', { ns: 'common' })}
{ menuItems.map(menuItem => (
{!isCurrentWorkspaceDatasetOperator && ( -
{menuItem.name}
+
{menuItem.name}
)}
{ @@ -186,7 +170,7 @@ export default function AccountSetting({ key={item.key} className={cn( 'mb-0.5 flex h-[37px] cursor-pointer items-center rounded-lg p-1 pl-3 text-sm', - activeMenu === item.key ? 'system-sm-semibold bg-state-base-active text-components-menu-item-text-active' : 'system-sm-medium text-components-menu-item-text', + activeMenu === item.key ? 'bg-state-base-active text-components-menu-item-text-active system-sm-semibold' : 'text-components-menu-item-text system-sm-medium', )} title={item.name} onClick={() => { @@ -213,25 +197,23 @@ export default function AccountSetting({ className="px-2" onClick={onCancel} > - + -
ESC
+
ESC
-
+
{activeItem?.name} {activeItem?.description && ( -
{activeItem?.description}
+
{activeItem?.description}
)}
{activeItem?.key === 'provider' && (
- setSearchValue(e.target.value)} +
diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 0df2f2601f..31f7058e20 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -2052,9 +2052,6 @@ "app/components/base/input/index.tsx": { "react-refresh/only-export-components": { "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 3 } }, "app/components/base/linked-apps-panel/index.tsx": { @@ -3994,9 +3991,6 @@ "app/components/header/account-setting/index.tsx": { "react-hooks-extra/no-direct-set-state-in-use-effect": { "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 7 } }, "app/components/header/account-setting/key-validator/declarations.ts": {