refactor(skill): replace @remixicon/react imports with CSS icon classes

Migrate all Remixicon component imports in workflow/skill to Tailwind CSS
icon utility classes (i-ri-*), reducing JS bundle size. Update MenuItem
to accept string icon classes alongside React components. Adjust test
selectors that relied on SVG element queries.
This commit is contained in:
yyh
2026-02-09 19:51:05 +08:00
parent db0c527ce8
commit 9e10b73b54
23 changed files with 60 additions and 95 deletions

View File

@ -4,7 +4,6 @@ import type { MoveHandler, NodeApi, NodeRendererProps, TreeApi } from 'react-arb
import type { TreeNodeData } from '../../type'
import type { OpensObject } from '@/app/components/workflow/store/workflow/skill-editor/file-tree-slice'
import type { AppAssetTreeView } from '@/types/app-asset'
import { RiDragDropLine } from '@remixicon/react'
import { useIsMutating } from '@tanstack/react-query'
import { useSize } from 'ahooks'
import * as React from 'react'
@ -82,7 +81,7 @@ const DropTip = () => {
const { t } = useTranslation('workflow')
return (
<div className="flex shrink-0 items-center justify-center gap-2 py-4 text-text-quaternary">
<RiDragDropLine className="size-4" aria-hidden="true" />
<span className="i-ri-drag-drop-line size-4" aria-hidden="true" />
<span className="system-xs-regular">
{t('skillSidebar.dropTip')}
</span>

View File

@ -68,7 +68,7 @@ describe('MenuItem', () => {
// Arrange
const tooltipText = 'Show help'
const { container } = renderMenuItem({ tooltip: tooltipText })
const tooltipIcon = container.querySelector('svg.text-text-quaternary')
const tooltipIcon = container.querySelector('.i-ri-question-line')
// Act
expect(tooltipIcon).toBeTruthy()
@ -103,7 +103,7 @@ describe('MenuItem', () => {
// Arrange
const onClick = vi.fn()
const { container } = renderMenuItem({ onClick, tooltip: 'Help' })
const tooltipIcon = container.querySelector('svg.text-text-quaternary')
const tooltipIcon = container.querySelector('.i-ri-question-line')
// Act
expect(tooltipIcon).toBeTruthy()

View File

@ -1,7 +1,6 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import { RiQuestionLine } from '@remixicon/react'
import { cva } from 'class-variance-authority'
import * as React from 'react'
import Tooltip from '@/app/components/base/tooltip'
@ -52,7 +51,7 @@ const labelVariants = cva('text-text-secondary system-sm-regular', {
})
export type MenuItemProps = {
icon: React.ElementType
icon: React.ElementType | string
label: string
kbd?: readonly string[]
onClick: React.MouseEventHandler<HTMLButtonElement>
@ -73,7 +72,9 @@ const MenuItem = ({ icon: Icon, label, kbd, onClick, disabled, variant, tooltip
disabled={disabled}
className={cn(menuItemVariants({ variant }))}
>
<Icon className={cn(iconVariants({ variant }))} aria-hidden="true" />
{typeof Icon === 'string'
? <span className={cn(Icon, iconVariants({ variant }))} aria-hidden="true" />
: <Icon className={cn(iconVariants({ variant }))} aria-hidden="true" />}
<span className={cn(labelVariants({ variant }), 'flex-1 text-left')}>{label}</span>
{kbd && kbd.length > 0 && <ShortcutsName keys={kbd} textColor="secondary" />}
{tooltip && (
@ -88,7 +89,7 @@ const MenuItem = ({ icon: Icon, label, kbd, onClick, disabled, variant, tooltip
event.stopPropagation()
}}
>
<RiQuestionLine className="size-4 text-text-quaternary hover:text-text-tertiary" />
<span className="i-ri-question-line size-4 text-text-quaternary hover:text-text-tertiary" />
</span>
</Tooltip>
)}

View File

@ -3,14 +3,6 @@
import type { NodeApi, TreeApi } from 'react-arborist'
import type { NodeMenuType } from '../../constants'
import type { TreeNodeData } from '../../type'
import {
RiClipboardLine,
RiDeleteBinLine,
RiEdit2Line,
RiFolderUploadLine,
RiScissorsLine,
RiUploadLine,
} from '@remixicon/react'
import dynamic from 'next/dynamic'
import * as React from 'react'
import { useCallback, useState } from 'react'
@ -146,7 +138,7 @@ const NodeMenu = ({
disabled={isLoading}
/>
<MenuItem
icon={RiFolderUploadLine}
icon="i-ri-folder-upload-line"
label={t('skillSidebar.menu.uploadFolder')}
onClick={() => folderInputRef.current?.click()}
disabled={isLoading}
@ -156,7 +148,7 @@ const NodeMenu = ({
<>
<div className="my-1 h-px bg-divider-subtle" />
<MenuItem
icon={RiUploadLine}
icon="i-ri-upload-line"
label={t('skillSidebar.menu.importSkills')}
onClick={() => setIsImportModalOpen(true)}
disabled={isLoading}
@ -184,7 +176,7 @@ const NodeMenu = ({
{!isRoot && (
<>
<MenuItem
icon={RiScissorsLine}
icon="i-ri-scissors-line"
label={t('skillSidebar.menu.cut')}
kbd={KBD_CUT}
onClick={handleCut}
@ -195,7 +187,7 @@ const NodeMenu = ({
{isFolder && hasClipboard && (
<MenuItem
icon={RiClipboardLine}
icon="i-ri-clipboard-line"
label={t('skillSidebar.menu.paste')}
kbd={KBD_PASTE}
onClick={handlePaste}
@ -207,13 +199,13 @@ const NodeMenu = ({
<>
<div className="my-1 h-px bg-divider-subtle" />
<MenuItem
icon={RiEdit2Line}
icon="i-ri-edit-2-line"
label={t('skillSidebar.menu.rename')}
onClick={handleRename}
disabled={isLoading}
/>
<MenuItem
icon={RiDeleteBinLine}
icon="i-ri-delete-bin-line"
label={t('skillSidebar.menu.delete')}
onClick={handleDeleteClick}
disabled={isLoading}

View File

@ -29,7 +29,7 @@ describe('TreeNodeIcon', () => {
// Act
const toggleButton = screen.getByRole('button', { name: /workflow\.skillSidebar\.toggleFolder/i })
const icon = toggleButton.querySelector('svg')
const icon = toggleButton.querySelector('[aria-hidden="true"]')
// Assert
expect(toggleButton).toBeInTheDocument()
@ -50,7 +50,7 @@ describe('TreeNodeIcon', () => {
// Act
const toggleButton = screen.getByRole('button', { name: /workflow\.skillSidebar\.toggleFolder/i })
const icon = toggleButton.querySelector('svg')
const icon = toggleButton.querySelector('[aria-hidden="true"]')
// Assert
expect(icon).toHaveClass('text-text-secondary')

View File

@ -3,7 +3,6 @@
// Icon rendering for tree nodes (folder/file icons with dirty indicator)
import type { FileAppearanceType } from '@/app/components/base/file-uploader/types'
import { RiFolderLine, RiFolderOpenLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon'
import { cn } from '@/utils/classnames'
@ -41,8 +40,8 @@ export const TreeNodeIcon = ({
)}
>
{isOpen
? <RiFolderOpenLine className="size-4 text-text-accent" aria-hidden="true" />
: <RiFolderLine className="size-4 text-text-secondary" aria-hidden="true" />}
? <span className="i-ri-folder-open-line size-4 text-text-accent" aria-hidden="true" />
: <span className="i-ri-folder-line size-4 text-text-secondary" aria-hidden="true" />}
</button>
)
}

View File

@ -2,7 +2,6 @@
import type { NodeRendererProps } from 'react-arborist'
import type { TreeNodeData } from '../../type'
import { RiMoreFill } from '@remixicon/react'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -171,7 +170,7 @@ const TreeNode = ({ node, style, dragHandle, treeChildren }: TreeNodeProps) => {
)}
aria-label={t('skillSidebar.menu.moreActions')}
>
<RiMoreFill className="size-4 text-text-tertiary" aria-hidden="true" />
<span className="i-ri-more-fill size-4 text-text-tertiary" aria-hidden="true" />
</button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-[100]">

View File

@ -1,12 +1,6 @@
'use client'
import type { ReactNode } from 'react'
import {
RiAlertFill,
RiCheckboxCircleFill,
RiCloseLine,
RiUploadCloud2Line,
} from '@remixicon/react'
import { memo, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
@ -74,13 +68,13 @@ const UploadStatusTooltip = ({ fallback }: UploadStatusTooltipProps) => {
<div className="relative z-10 shrink-0">
{uploadStatus === 'uploading' && (
<RiUploadCloud2Line className="size-6 text-text-accent" />
<span className="i-ri-upload-cloud-2-line size-6 text-text-accent" />
)}
{uploadStatus === 'success' && (
<RiCheckboxCircleFill className="size-5 text-text-success" />
<span className="i-ri-checkbox-circle-fill size-5 text-text-success" />
)}
{uploadStatus === 'partial_error' && (
<RiAlertFill className="size-5 text-text-warning" />
<span className="i-ri-alert-fill size-5 text-text-warning" />
)}
</div>
@ -118,7 +112,7 @@ const UploadStatusTooltip = ({ fallback }: UploadStatusTooltipProps) => {
className="relative z-10 shrink-0 rounded p-0.5 text-text-tertiary hover:text-text-secondary focus-visible:outline focus-visible:outline-2 focus-visible:outline-state-accent-solid"
onClick={handleClose}
>
<RiCloseLine className="size-4" />
<span className="i-ri-close-line size-4" />
</button>
</div>
</div>