From e2c52c9b0f60a43d34c8af3498ba0b965ae1eaca Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 18 May 2026 13:27:42 +0800 Subject: [PATCH 01/23] refactor: migrate checkbox to dify-ui (#36295) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 8 - .../base/notion-page-selector-flow.test.tsx | 2 +- .../datasets/document-management.test.tsx | 66 +-- .../app/annotation/__tests__/list.spec.tsx | 12 +- .../__tests__/index.spec.tsx | 5 +- .../annotation/add-annotation-modal/index.tsx | 10 +- web/app/components/app/annotation/list.tsx | 24 +- .../__tests__/form-fields.spec.tsx | 17 +- .../config-var/config-modal/form-fields.tsx | 24 +- .../components/app/switch-app-modal/index.tsx | 16 +- .../components/apps/__tests__/list.spec.tsx | 2 +- web/app/components/apps/list.tsx | 10 +- .../inputs-form/__tests__/content.spec.tsx | 2 +- .../base/checkbox/__tests__/index.spec.tsx | 110 ----- .../__tests__/indeterminate-icon.spec.tsx | 17 - .../checkbox/assets/indeterminate-icon.tsx | 11 - .../base/checkbox/index.stories.tsx | 399 ------------------ web/app/components/base/checkbox/index.tsx | 69 --- .../base/form/components/field/checkbox.tsx | 26 +- .../markdown-blocks/__tests__/form.spec.tsx | 22 +- .../components/base/markdown-blocks/form.tsx | 20 +- .../__tests__/base.spec.tsx | 10 +- .../page-selector/__tests__/index.spec.tsx | 18 +- .../page-selector/__tests__/page-row.spec.tsx | 2 +- .../page-selector/page-row.tsx | 6 +- .../components/general-chunking-options.tsx | 44 +- .../components/parent-child-options.tsx | 18 +- .../create/website/__tests__/base.spec.tsx | 12 +- .../__tests__/checkbox-with-label.spec.tsx | 4 +- .../__tests__/crawled-result-item.spec.tsx | 8 +- .../base/__tests__/crawled-result.spec.tsx | 70 +-- .../website/base/checkbox-with-label.tsx | 33 +- .../website/base/crawled-result-item.tsx | 47 +-- .../create/website/base/crawled-result.tsx | 2 - .../firecrawl/__tests__/index.spec.tsx | 6 +- .../firecrawl/__tests__/options.spec.tsx | 30 +- .../create/website/firecrawl/options.tsx | 2 - .../jina-reader/__tests__/index.spec.tsx | 4 +- .../jina-reader/__tests__/options.spec.tsx | 21 +- .../create/website/jina-reader/options.tsx | 2 - .../watercrawl/__tests__/index.spec.tsx | 4 +- .../watercrawl/__tests__/options.spec.tsx | 33 +- .../create/website/watercrawl/options.tsx | 2 - .../components/__tests__/list.spec.tsx | 23 +- .../document-list/__tests__/index.spec.tsx | 8 +- .../__tests__/document-table-row.spec.tsx | 60 ++- .../components/document-table-row.tsx | 19 +- .../__tests__/use-document-selection.spec.ts | 193 --------- .../hooks/use-document-selection.ts | 28 -- .../datasets/documents/components/list.tsx | 32 +- .../__tests__/index.spec.tsx | 10 +- .../actions/__tests__/index.spec.tsx | 112 +++-- .../create-from-pipeline/actions/index.tsx | 10 +- .../page-selector/__tests__/index.spec.tsx | 21 +- .../file-list/__tests__/index.spec.tsx | 12 +- .../file-list/list/__tests__/index.spec.tsx | 51 +-- .../file-list/list/__tests__/item.spec.tsx | 20 +- .../online-drive/file-list/list/item.tsx | 24 +- .../__tests__/checkbox-with-label.spec.tsx | 8 +- .../__tests__/crawled-result-item.spec.tsx | 8 +- .../base/__tests__/index.spec.tsx | 127 +++--- .../base/checkbox-with-label.tsx | 27 +- .../base/crawled-result-item.tsx | 70 +-- .../__tests__/use-datasource-actions.spec.ts | 6 +- .../hooks/use-datasource-actions.ts | 8 +- .../steps/step-one-content.tsx | 2 +- .../detail/__tests__/new-segment.spec.tsx | 25 +- .../__tests__/new-child-segment.spec.tsx | 21 +- .../completed/__tests__/segment-list.spec.tsx | 6 +- .../common/__tests__/add-another.spec.tsx | 113 +++-- .../detail/completed/common/add-another.tsx | 18 +- .../components/__tests__/menu-bar.spec.tsx | 6 +- .../detail/completed/components/menu-bar.tsx | 7 +- .../detail/completed/new-child-segment.tsx | 4 +- .../detail/completed/segment-list.tsx | 7 +- .../__tests__/general-list-skeleton.spec.tsx | 7 +- .../paragraph-list-skeleton.spec.tsx | 7 +- .../skeleton/general-list-skeleton.tsx | 5 +- .../skeleton/paragraph-list-skeleton.tsx | 5 +- .../datasets/documents/detail/new-segment.tsx | 4 +- .../base/__tests__/loading-error.spec.tsx | 4 +- .../base/__tests__/loading.spec.tsx | 4 +- .../install-plugin/base/loading-error.tsx | 6 +- .../plugins/install-plugin/base/loading.tsx | 6 +- .../install-bundle/__tests__/index.spec.tsx | 36 +- .../item/__tests__/loaded-item.spec.tsx | 20 +- .../install-bundle/item/loaded-item.tsx | 5 +- .../install-bundle/steps/install.tsx | 10 +- .../search-box/__tests__/tags-filter.spec.tsx | 4 - .../marketplace/search-box/tags-filter.tsx | 8 +- .../__tests__/index.spec.tsx | 8 +- .../filter-management/category-filter.tsx | 8 +- .../filter-management/tag-filter.tsx | 8 +- .../__tests__/index.spec.tsx | 31 +- .../__tests__/tool-item.spec.tsx | 18 +- .../auto-update-setting/tool-item.tsx | 5 +- .../tools/labels/__tests__/selector.spec.tsx | 4 +- web/app/components/tools/labels/selector.tsx | 10 +- .../workflow/dsl-export-confirm-modal.tsx | 22 +- .../components/before-run-form/bool-input.tsx | 14 +- .../nodes/_base/components/file-type-item.tsx | 6 +- .../components/generic-table.tsx | 6 +- .../education-apply/education-apply-page.tsx | 14 +- 103 files changed, 757 insertions(+), 1864 deletions(-) delete mode 100644 web/app/components/base/checkbox/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/checkbox/assets/__tests__/indeterminate-icon.spec.tsx delete mode 100644 web/app/components/base/checkbox/assets/indeterminate-icon.tsx delete mode 100644 web/app/components/base/checkbox/index.stories.tsx delete mode 100644 web/app/components/base/checkbox/index.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index cd737f35cc..c1835f888f 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -789,14 +789,6 @@ "count": 10 } }, - "web/app/components/base/checkbox/index.stories.tsx": { - "no-console": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/chip/index.tsx": { "ts/no-explicit-any": { "count": 3 diff --git a/web/__tests__/base/notion-page-selector-flow.test.tsx b/web/__tests__/base/notion-page-selector-flow.test.tsx index ef813ee4bc..34f4c988e1 100644 --- a/web/__tests__/base/notion-page-selector-flow.test.tsx +++ b/web/__tests__/base/notion-page-selector-flow.test.tsx @@ -100,7 +100,7 @@ describe('Base Notion Page Selector Flow', () => { />, ) - await user.click(screen.getByTestId('checkbox-notion-page-checkbox-root-1')) + await user.click(screen.getByRole('checkbox', { name: 'Root 1' })) expect(onSelect).toHaveBeenLastCalledWith(expect.arrayContaining([ expect.objectContaining({ page_id: 'root-1', workspace_id: 'w1' }), diff --git a/web/__tests__/datasets/document-management.test.tsx b/web/__tests__/datasets/document-management.test.tsx index 96937332a3..8ce661ff4f 100644 --- a/web/__tests__/datasets/document-management.test.tsx +++ b/web/__tests__/datasets/document-management.test.tsx @@ -184,7 +184,7 @@ describe('Document Management Flow', () => { }) describe('Document Selection Integration', () => { - it('should manage selection state externally', () => { + it('should keep checkbox selection state owned outside the hook', () => { const docs = [ createDoc({ id: 'doc-1' }), createDoc({ id: 'doc-2' }), @@ -198,62 +198,9 @@ describe('Document Management Flow', () => { onSelectedIdChange, })) - expect(result.current.isAllSelected).toBe(false) - expect(result.current.isSomeSelected).toBe(false) - }) - - it('should select all documents', () => { - const docs = [ - createDoc({ id: 'doc-1' }), - createDoc({ id: 'doc-2' }), - ] - const onSelectedIdChange = vi.fn() - - const { result } = renderHook(() => useDocumentSelection({ - documents: docs, - selectedIds: [], - onSelectedIdChange, - })) - - act(() => { - result.current.onSelectAll() - }) - - expect(onSelectedIdChange).toHaveBeenCalledWith( - expect.arrayContaining(['doc-1', 'doc-2']), - ) - }) - - it('should detect all-selected state', () => { - const docs = [ - createDoc({ id: 'doc-1' }), - createDoc({ id: 'doc-2' }), - ] - - const { result } = renderHook(() => useDocumentSelection({ - documents: docs, - selectedIds: ['doc-1', 'doc-2'], - onSelectedIdChange: vi.fn(), - })) - - expect(result.current.isAllSelected).toBe(true) - }) - - it('should detect partial selection', () => { - const docs = [ - createDoc({ id: 'doc-1' }), - createDoc({ id: 'doc-2' }), - createDoc({ id: 'doc-3' }), - ] - - const { result } = renderHook(() => useDocumentSelection({ - documents: docs, - selectedIds: ['doc-1'], - onSelectedIdChange: vi.fn(), - })) - - expect(result.current.isSomeSelected).toBe(true) - expect(result.current.isAllSelected).toBe(false) + expect(result.current.downloadableSelectedIds).toEqual([]) + expect(result.current.hasErrorDocumentsSelected).toBe(false) + expect(onSelectedIdChange).not.toHaveBeenCalled() }) it('should identify downloadable selected documents (FILE type only)', () => { @@ -311,8 +258,9 @@ describe('Document Management Flow', () => { expect(sortResult.current.sortField).toBe('created_at') expect(sortResult.current.sortOrder).toBe('desc') - // Selection starts empty - expect(selResult.current.isAllSelected).toBe(false) + // Selection-derived batch metadata starts empty. + expect(selResult.current.downloadableSelectedIds).toEqual([]) + expect(selResult.current.hasErrorDocumentsSelected).toBe(false) }) }) }) diff --git a/web/app/components/app/annotation/__tests__/list.spec.tsx b/web/app/components/app/annotation/__tests__/list.spec.tsx index ad6a1e5e9c..883c1adfe0 100644 --- a/web/app/components/app/annotation/__tests__/list.spec.tsx +++ b/web/app/components/app/annotation/__tests__/list.spec.tsx @@ -19,8 +19,6 @@ const createAnnotation = (overrides: Partial = {}): AnnotationIt hit_count: overrides.hit_count ?? 2, }) -const getCheckboxes = (container: HTMLElement) => container.querySelectorAll('[data-testid^="checkbox"]') - describe('List', () => { beforeEach(() => { vi.clearAllMocks() @@ -51,7 +49,7 @@ describe('List', () => { it('should toggle single and bulk selection states', () => { const list = [createAnnotation({ id: 'a', question: 'A' }), createAnnotation({ id: 'b', question: 'B' })] const onSelectedIdsChange = vi.fn() - const { container, rerender } = render( + const { rerender } = render( { />, ) - const checkboxes = getCheckboxes(container) - fireEvent.click(checkboxes[1]!) + fireEvent.click(screen.getByRole('checkbox', { name: 'A' })) expect(onSelectedIdsChange).toHaveBeenCalledWith(['a']) rerender( @@ -78,11 +75,10 @@ describe('List', () => { onCancel={vi.fn()} />, ) - const updatedCheckboxes = getCheckboxes(container) - fireEvent.click(updatedCheckboxes[1]!) + fireEvent.click(screen.getByRole('checkbox', { name: 'A' })) expect(onSelectedIdsChange).toHaveBeenCalledWith([]) - fireEvent.click(updatedCheckboxes[0]!) + fireEvent.click(screen.getByRole('checkbox', { name: 'common.operation.selectAll' })) expect(onSelectedIdsChange).toHaveBeenCalledWith(['a', 'b']) }) diff --git a/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx b/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx index 497c623f3e..00f91ab429 100644 --- a/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx @@ -91,7 +91,7 @@ describe('AddAnnotationModal', () => { typeQuestion('Question value') typeAnswer('Answer value') - fireEvent.click(screen.getByTestId('checkbox-create-next-checkbox')) + fireEvent.click(screen.getByText('appAnnotation.addModal.createNext')) await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' })) @@ -106,8 +106,7 @@ describe('AddAnnotationModal', () => { typeQuestion('Question value') typeAnswer('Answer value') - const createNextToggle = screen.getByText('appAnnotation.addModal.createNext').previousElementSibling as HTMLElement - fireEvent.click(createNextToggle) + fireEvent.click(screen.getByText('appAnnotation.addModal.createNext')) await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'common.operation.add' })) diff --git a/web/app/components/app/annotation/add-annotation-modal/index.tsx b/web/app/components/app/annotation/add-annotation-modal/index.tsx index 53f3cd5bdd..f94b072e39 100644 --- a/web/app/components/app/annotation/add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/index.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react' import type { AnnotationItemBasic } from '../type' import { Button } from '@langgenius/dify-ui/button' +import { Checkbox } from '@langgenius/dify-ui/checkbox' import { Drawer, DrawerBackdrop, @@ -16,7 +17,6 @@ import { toast } from '@langgenius/dify-ui/toast' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import Checkbox from '@/app/components/base/checkbox' import AnnotationFull from '@/app/components/billing/annotation-full' import { useProviderContext } from '@/context/provider-context' import EditItem, { EditItemType } from './edit-item' @@ -128,10 +128,10 @@ const AddAnnotationModal: FC = ({ )}
-
- setIsCreateNext(!isCreateNext)} /> -
{t('addModal.createNext', { ns: 'appAnnotation' })}
-
+
diff --git a/web/app/components/app/annotation/list.tsx b/web/app/components/app/annotation/list.tsx index caad4dd523..fa12c95ae3 100644 --- a/web/app/components/app/annotation/list.tsx +++ b/web/app/components/app/annotation/list.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' import type { AnnotationItem } from './type' +import { Checkbox } from '@langgenius/dify-ui/checkbox' import { cn } from '@langgenius/dify-ui/cn' import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' import * as React from 'react' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' -import Checkbox from '@/app/components/base/checkbox' import useTimestamp from '@/hooks/use-timestamp' import BatchAction from './batch-action' import RemoveAnnotationConfirmModal from './remove-annotation-confirm-modal' @@ -44,15 +44,15 @@ const List: FC = ({ return list.some(item => selectedIds.includes(item.id)) }, [list, selectedIds]) - const handleSelectAll = useCallback(() => { + const handleSelectAll = useCallback((checked: boolean) => { const currentPageIds = list.map(item => item.id) const otherPageIds = selectedIds.filter(id => !currentPageIds.includes(id)) - if (isAllSelected) - onSelectedIdsChange(otherPageIds) - else + if (checked) onSelectedIdsChange([...otherPageIds, ...currentPageIds]) - }, [isAllSelected, list, selectedIds, onSelectedIdsChange]) + else + onSelectedIdsChange(otherPageIds) + }, [list, selectedIds, onSelectedIdsChange]) return ( <> @@ -65,7 +65,8 @@ const List: FC = ({ className="mr-2" checked={isAllSelected} indeterminate={!isAllSelected && isSomeSelected} - onCheck={handleSelectAll} + aria-label={t('operation.selectAll', { ns: 'common' })} + onCheckedChange={handleSelectAll} /> {t('table.header.question', { ns: 'appAnnotation' })} @@ -90,11 +91,12 @@ const List: FC = ({ { - if (selectedIds.includes(item.id)) - onSelectedIdsChange(selectedIds.filter(id => id !== item.id)) - else + aria-label={item.question} + onCheckedChange={(checked) => { + if (checked) onSelectedIdsChange([...selectedIds, item.id]) + else + onSelectedIdsChange(selectedIds.filter(id => id !== item.id)) }} /> diff --git a/web/app/components/app/configuration/config-var/config-modal/__tests__/form-fields.spec.tsx b/web/app/components/app/configuration/config-var/config-modal/__tests__/form-fields.spec.tsx index cdb1d17833..7a77821c18 100644 --- a/web/app/components/app/configuration/config-var/config-modal/__tests__/form-fields.spec.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/__tests__/form-fields.spec.tsx @@ -70,12 +70,6 @@ vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', ( ), })) -vi.mock('@/app/components/base/checkbox', () => ({ - default: ({ onCheck, checked }: { onCheck: () => void, checked: boolean }) => ( - - ), -})) - vi.mock('@langgenius/dify-ui/select', async (importOriginal) => { const actual = await importOriginal() @@ -230,7 +224,7 @@ describe('ConfigModalFormFields', () => { expect(screen.queryByText('variableConfig.hiddenDescription')).not.toBeInTheDocument() fireEvent.click(screen.getByText('single-file-setting')) fireEvent.click(screen.getByText('upload-file')) - fireEvent.click(screen.getAllByText('unchecked')[0]!) + fireEvent.click(screen.getByRole('checkbox', { name: 'variableConfig.required' })) expect(singleFileProps.onFilePayloadChange).toHaveBeenCalledWith({ number_limits: 1 }) expect(singleFileProps.payloadChangeHandlers.default).toHaveBeenCalledWith(expect.objectContaining({ @@ -416,17 +410,14 @@ describe('ConfigModalFormFields', () => { requiredProps.tempPayload = { ...requiredProps.tempPayload, type: InputVarType.textInput, required: true, hide: false } const { unmount } = render() - const buttons = screen.getAllByRole('button') - const hideButton = buttons.find(btn => btn.textContent === 'unchecked' && btn !== buttons[0]) - expect(hideButton).toBeDefined() + expect(screen.getByRole('checkbox', { name: 'variableConfig.hidden' })).toHaveAttribute('aria-disabled', 'true') unmount() const hideProps = createBaseProps() hideProps.tempPayload = { ...hideProps.tempPayload, type: InputVarType.textInput, required: false, hide: true } render() - const allButtons = screen.getAllByRole('button') - const checkedHideButton = allButtons.find(btn => btn.textContent === 'checked') - expect(checkedHideButton).toBeDefined() + expect(screen.getByRole('checkbox', { name: 'variableConfig.required' })).toHaveAttribute('aria-disabled', 'true') + expect(screen.getByRole('checkbox', { name: 'variableConfig.hidden' })).toHaveAttribute('aria-checked', 'true') }) }) diff --git a/web/app/components/app/configuration/config-var/config-modal/form-fields.tsx b/web/app/components/app/configuration/config-var/config-modal/form-fields.tsx index 4bd938c3f6..c932c15896 100644 --- a/web/app/components/app/configuration/config-var/config-modal/form-fields.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/form-fields.tsx @@ -3,6 +3,7 @@ import type { ChangeEvent, FC } from 'react' import type { Item as SelectOptionItem } from './type-select' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { InputVar, UploadFileSetting } from '@/app/components/workflow/types' +import { Checkbox } from '@langgenius/dify-ui/checkbox' import { Select, SelectContent, @@ -14,7 +15,6 @@ import { } from '@langgenius/dify-ui/select' import * as React from 'react' import { Trans } from 'react-i18next' -import Checkbox from '@/app/components/base/checkbox' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import { Infotip } from '@/app/components/base/infotip' import Input from '@/app/components/base/input' @@ -232,16 +232,26 @@ const ConfigModalFormFields: FC = ({ )} -
- onPayloadChange('required')(!tempPayload.required)} /> +
+ {!isFileInput && ( -
- onPayloadChange('hide')(!tempPayload.hide)} /> -
+
+ +
}
- setRemoveOriginal(!removeOriginal)} /> - +
diff --git a/web/app/components/apps/__tests__/list.spec.tsx b/web/app/components/apps/__tests__/list.spec.tsx index 0c6f1702d7..75442c1ebd 100644 --- a/web/app/components/apps/__tests__/list.spec.tsx +++ b/web/app/components/apps/__tests__/list.spec.tsx @@ -415,7 +415,7 @@ describe('List', () => { it('should handle checkbox change', () => { renderList() - const checkbox = screen.getByTestId('checkbox-undefined') + const checkbox = screen.getByRole('checkbox', { name: 'app.showMyCreatedAppsOnly' }) fireEvent.click(checkbox) expect(mockSetIsCreatedByMe).toHaveBeenCalledWith(true) diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx index e2e8e737fc..b1145fe5de 100644 --- a/web/app/components/apps/list.tsx +++ b/web/app/components/apps/list.tsx @@ -2,12 +2,12 @@ import type { FC } from 'react' import type { AppListQuery } from '@/contract/console/apps' +import { Checkbox } from '@langgenius/dify-ui/checkbox' import { cn } from '@langgenius/dify-ui/cn' import { keepPreviousData, useInfiniteQuery, useSuspenseQuery } from '@tanstack/react-query' import { useDebounce } from 'ahooks' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Checkbox from '@/app/components/base/checkbox' import Input from '@/app/components/base/input' import TabSliderNew from '@/app/components/base/tab-slider-new' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' @@ -158,9 +158,9 @@ const List: FC = ({ return () => observer?.disconnect() }, [isLoading, isFetchingNextPage, fetchNextPage, error, hasNextPage, isCurrentWorkspaceDatasetOperator]) - const handleCreatedByMeChange = useCallback(() => { - setIsCreatedByMe(!isCreatedByMe) - }, [isCreatedByMe, setIsCreatedByMe]) + const handleCreatedByMeChange = useCallback((checked: boolean) => { + setIsCreatedByMe(checked) + }, [setIsCreatedByMe]) const pages = useMemo(() => data?.pages ?? [], [data?.pages]) const apps = useMemo(() => pages.flatMap(({ data: pageApps }) => pageApps), [pages]) @@ -204,7 +204,7 @@ const List: FC = ({ />