mirror of
https://github.com/langgenius/dify.git
synced 2026-10-30 18:34:29 +08:00
refactor(web): migrate app icon commands (#40646)
This commit is contained in:
@@ -44,6 +44,31 @@ jobs:
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
browser-test:
|
||||
name: Web Browser Tests
|
||||
runs-on: depot-ubuntu-24.04-4
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: ./web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup web environment
|
||||
uses: ./.github/actions/setup-web
|
||||
|
||||
- name: Install Chromium for Browser Mode
|
||||
timeout-minutes: 15
|
||||
run: vp exec playwright install --with-deps --only-shell chromium
|
||||
|
||||
- name: Run browser tests
|
||||
run: vp test run --config vitest.browser.config.ts --silent=passed-only
|
||||
|
||||
merge-reports:
|
||||
name: Merge Test Reports
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
@@ -15,6 +15,8 @@ const config: KnipConfig = {
|
||||
'tsslint.config.ts',
|
||||
'dev-proxy.config.ts',
|
||||
'plugins/eslint/index.js',
|
||||
'vitest.browser.config.ts',
|
||||
'vitest.browser.setup.ts',
|
||||
],
|
||||
project: [
|
||||
'**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts,css,mdx}!',
|
||||
|
||||
@@ -979,14 +979,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx": {
|
||||
"jsx_a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx_a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx": {
|
||||
"jsx_a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
|
||||
Generated
+12
@@ -1594,6 +1594,12 @@ importers:
|
||||
'@vitejs/plugin-rsc':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.33(@voidzero-dev/vite-plus-core@0.2.8(@types/node@25.9.5)(@typescript/typescript6@6.0.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.8)(yaml@2.9.0))(react-dom@19.2.8(react@19.2.8))(react-server-dom-webpack@19.2.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
|
||||
'@vitest/browser':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(@voidzero-dev/vite-plus-core@0.2.8(@types/node@25.9.5)(@typescript/typescript6@6.0.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.8)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/browser-playwright':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(@voidzero-dev/vite-plus-core@0.2.8(@types/node@25.9.5)(@typescript/typescript6@6.0.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.8)(yaml@2.9.0))(playwright@1.62.1)(vitest@4.1.10)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
||||
@@ -1606,6 +1612,9 @@ importers:
|
||||
happy-dom:
|
||||
specifier: 'catalog:'
|
||||
version: 20.11.1
|
||||
playwright:
|
||||
specifier: 'catalog:'
|
||||
version: 1.62.1
|
||||
postcss:
|
||||
specifier: 'catalog:'
|
||||
version: 8.5.25
|
||||
@@ -1642,6 +1651,9 @@ importers:
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(@types/node@25.9.5)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/coverage-v8@4.1.10)(@voidzero-dev/vite-plus-core@0.2.8(@types/node@25.9.5)(@typescript/typescript6@6.0.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.8)(yaml@2.9.0))(happy-dom@20.11.1)
|
||||
vitest-browser-react:
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10)
|
||||
vitest-canvas-mock:
|
||||
specifier: 'catalog:'
|
||||
version: 1.1.4(vitest@4.1.10)
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
@@ -127,11 +128,15 @@ const SnippetInfoDropdown = ({ snippet }: SnippetInfoDropdownProps) => {
|
||||
<>
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $['operation.more'], { ns: 'common' })}
|
||||
className="action-btn action-btn-m size-6 rounded-md text-text-tertiary data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
render={
|
||||
<IconButton
|
||||
aria-label={t(($) => $['operation.more'], { ns: 'common' })}
|
||||
className="rounded-md data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[180px] p-1">
|
||||
{canCreateAndModifySnippet && (
|
||||
<>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { GenRes } from '@/service/debug'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiClipboardLine } from '@remixicon/react'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -52,16 +52,17 @@ const Result: FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
<IconButton
|
||||
aria-label={t(($) => $['operation.copy'], { ns: 'common' })}
|
||||
className="px-2"
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
copy(current.modified)
|
||||
toast.success(t(($) => $['actionMsg.copySuccessfully'], { ns: 'common' }))
|
||||
}}
|
||||
>
|
||||
<RiClipboardLine aria-hidden="true" className="size-4 text-text-secondary" />
|
||||
</Button>
|
||||
<span aria-hidden="true" className="i-ri-clipboard-line size-4 text-text-secondary" />
|
||||
</IconButton>
|
||||
<Button variant="primary" onClick={onApply}>
|
||||
{t(($) => $['generate.apply'], { ns: 'appDebug' })}
|
||||
</Button>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { Field, FieldControl, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import { Tabs, TabsList, TabsPanel, TabsTab } from '@langgenius/dify-ui/tabs'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@@ -259,16 +260,16 @@ function CreateFromDSLModal({
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $.importApp, { ns: 'app' })}
|
||||
</DialogTitle>
|
||||
<Button
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="small"
|
||||
size="lg"
|
||||
aria-label={t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
className="size-8 p-0"
|
||||
className="rounded-md"
|
||||
disabled={isImporting}
|
||||
onClick={onClose}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-5 text-text-tertiary" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
</div>
|
||||
<Form<ImportFormValues> ref={formRef} onFormSubmit={handleSubmit}>
|
||||
<Tabs value={currentTab} onValueChange={handleTabChange}>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { ChangeEvent, DragEvent, MouseEvent, RefObject } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useId, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -191,17 +192,16 @@ export function Uploader({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center pr-3 opacity-0 group-focus-within:opacity-100 group-hover:opacity-100">
|
||||
<Button
|
||||
<IconButton
|
||||
ref={removeButtonRef}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="size-6 p-0"
|
||||
size="md"
|
||||
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
|
||||
disabled={disabled}
|
||||
onClick={removeFile}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4 text-text-tertiary" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { Fragment, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getWorkflowVersionName } from '@/app/components/workflow/utils/version'
|
||||
@@ -136,14 +137,14 @@ export function EnvironmentRowActions({
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button
|
||||
size="small"
|
||||
<IconButton
|
||||
size="md"
|
||||
variant="secondary"
|
||||
aria-label={`${row.environment.display_name} · ${t(($) => $['deployTab.moreActions'])}`}
|
||||
className="w-6 shrink-0 px-0"
|
||||
className="shrink-0"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-50">
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import type { FileEntity } from '../../types'
|
||||
import { page } from 'vite-plus/test/browser'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import FileImageItem from '../file-image-item'
|
||||
|
||||
vi.mock('@/utils/download', () => ({
|
||||
downloadUrl: vi.fn(),
|
||||
}))
|
||||
|
||||
const file: FileEntity = {
|
||||
id: 'file-1',
|
||||
name: 'photo.png',
|
||||
size: 4096,
|
||||
type: 'image/png',
|
||||
progress: 100,
|
||||
transferMethod: 'local_file',
|
||||
supportFileType: 'image',
|
||||
uploadedId: 'uploaded-1',
|
||||
base64Url: 'data:image/png;base64,abc',
|
||||
url: 'https://example.com/photo.png',
|
||||
}
|
||||
|
||||
describe('FileImageItem pointer interaction', () => {
|
||||
it('keeps the preview clickable when the download action is visible', async () => {
|
||||
const screen = await render(<FileImageItem file={file} canPreview showDownloadAction />)
|
||||
const preview = screen.getByRole('button', { name: 'common.operation.view photo.png' })
|
||||
|
||||
await preview.hover()
|
||||
await preview.click()
|
||||
|
||||
await expect.element(page.getByRole('dialog')).toBeVisible()
|
||||
})
|
||||
})
|
||||
+12
@@ -1,5 +1,6 @@
|
||||
import type { FileEntity } from '../../types'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import FileImageItem from '../file-image-item'
|
||||
|
||||
@@ -123,6 +124,17 @@ describe('FileImageItem', () => {
|
||||
expect(screen.getByRole('button', { name: 'common.operation.download' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should keep image actions keyboard reachable', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<FileImageItem file={createFile()} showDeleteAction showDownloadAction />)
|
||||
|
||||
await user.tab()
|
||||
expect(screen.getByRole('button', { name: 'common.operation.remove' })).toHaveFocus()
|
||||
|
||||
await user.tab()
|
||||
expect(screen.getByRole('button', { name: 'common.operation.download' })).toHaveFocus()
|
||||
})
|
||||
|
||||
it('should call downloadUrl when download button is clicked', async () => {
|
||||
const { downloadUrl } = await import('@/utils/download')
|
||||
render(<FileImageItem file={createFile()} showDownloadAction />)
|
||||
|
||||
+44
-40
@@ -1,10 +1,8 @@
|
||||
import type { FileEntity } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { ProgressCircle } from '@langgenius/dify-ui/progress'
|
||||
import { RiCloseLine, RiDownloadLine } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ReplayLine } from '@/app/components/base/icons/src/vender/other'
|
||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||
import { downloadUrl } from '@/utils/download'
|
||||
import FileImageRender from '../file-image-render'
|
||||
@@ -30,33 +28,43 @@ const FileImageItem = ({
|
||||
const { id, progress, base64Url, url, name } = file
|
||||
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
||||
const download_url = url ? `${url}&as_attachment=true` : base64Url
|
||||
const image = (
|
||||
<FileImageRender
|
||||
className="h-17 w-17 shadow-md"
|
||||
imageUrl={base64Url || url || ''}
|
||||
showDownloadAction={showDownloadAction}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="group/file-image relative cursor-pointer"
|
||||
onClick={() => canPreview && setImagePreviewUrl(base64Url || url || '')}
|
||||
>
|
||||
{showDeleteAction && (
|
||||
<Button
|
||||
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
|
||||
className="absolute -top-1.5 -right-1.5 z-11 hidden size-5 rounded-full p-0 group-hover/file-image:flex"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRemove?.(id)
|
||||
}}
|
||||
<div className="group/file-image relative">
|
||||
{canPreview ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${t(($) => $['operation.view'], { ns: 'common' })} ${name}`}
|
||||
className="block border-0 bg-transparent p-0 focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
|
||||
onClick={() => setImagePreviewUrl(base64Url || url || '')}
|
||||
>
|
||||
<RiCloseLine
|
||||
className="size-4 text-components-button-secondary-text"
|
||||
{image}
|
||||
</button>
|
||||
) : (
|
||||
image
|
||||
)}
|
||||
{showDeleteAction && (
|
||||
<IconButton
|
||||
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="pointer-events-none absolute -top-1.5 -right-1.5 z-11 rounded-full opacity-0 group-focus-within/file-image:pointer-events-auto group-focus-within/file-image:opacity-100 group-hover/file-image:pointer-events-auto group-hover/file-image:opacity-100"
|
||||
onClick={() => onRemove?.(id)}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="i-ri-close-line size-4 text-components-button-secondary-text"
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
)}
|
||||
<FileImageRender
|
||||
className="h-17 w-17 shadow-md"
|
||||
imageUrl={base64Url || url || ''}
|
||||
showDownloadAction={showDownloadAction}
|
||||
/>
|
||||
{progress >= 0 && !fileIsUploaded(file) && (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center border-2 border-effects-image-frame bg-background-overlay-alt">
|
||||
<ProgressCircle
|
||||
@@ -68,32 +76,28 @@ const FileImageItem = ({
|
||||
)}
|
||||
{progress === -1 && (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center border-2 border-state-destructive-border bg-background-overlay-destructive">
|
||||
<button
|
||||
type="button"
|
||||
<IconButton
|
||||
size="sm"
|
||||
aria-label={t(($) => $['operation.retry'], { ns: 'common' })}
|
||||
className="size-5 border-none bg-transparent p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onReUpload?.(id)
|
||||
}}
|
||||
className="rounded-none hover:bg-transparent"
|
||||
onClick={() => onReUpload?.(id)}
|
||||
>
|
||||
<ReplayLine className="size-5" aria-hidden="true" />
|
||||
</button>
|
||||
<span aria-hidden="true" className="i-custom-vender-other-replay-line size-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
{showDownloadAction && (
|
||||
<div className="absolute inset-0.5 z-10 hidden bg-background-overlay-alt group-hover/file-image:block">
|
||||
<button
|
||||
type="button"
|
||||
<div className="pointer-events-none absolute inset-0.5 z-10 bg-background-overlay-alt opacity-0 group-focus-within/file-image:opacity-100 group-hover/file-image:opacity-100">
|
||||
<IconButton
|
||||
size="md"
|
||||
aria-label={t(($) => $['operation.download'], { ns: 'common' })}
|
||||
className="absolute right-0.5 bottom-0.5 flex size-6 items-center justify-center rounded-lg border-none bg-components-actionbar-bg p-0 shadow-md"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
className="pointer-events-none absolute right-0.5 bottom-0.5 rounded-lg bg-components-actionbar-bg shadow-md group-focus-within/file-image:pointer-events-auto group-hover/file-image:pointer-events-auto hover:bg-components-actionbar-bg"
|
||||
onClick={() => {
|
||||
downloadUrl({ url: download_url || '', fileName: name, target: '_blank' })
|
||||
}}
|
||||
>
|
||||
<RiDownloadLine className="size-4 text-text-tertiary" aria-hidden="true" />
|
||||
</button>
|
||||
<span aria-hidden="true" className="i-ri-download-line size-4 text-text-tertiary" />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { FileEntity } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { ProgressCircle } from '@langgenius/dify-ui/progress'
|
||||
@@ -53,16 +52,18 @@ const FileItem = ({
|
||||
)}
|
||||
>
|
||||
{showDeleteAction && (
|
||||
<Button
|
||||
<IconButton
|
||||
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
|
||||
className="absolute -top-1.5 -right-1.5 z-11 hidden size-5 rounded-full p-0 group-hover/file-item:flex"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="absolute -top-1.5 -right-1.5 z-11 hidden rounded-full group-hover/file-item:flex"
|
||||
onClick={() => onRemove?.(id)}
|
||||
>
|
||||
<span
|
||||
className="i-ri-close-line size-4 text-components-button-secondary-text"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
)}
|
||||
<div
|
||||
className="mb-1 line-clamp-2 h-8 cursor-pointer system-xs-medium break-all text-text-tertiary"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DifyLogo } from '../../base/logo/dify-logo'
|
||||
@@ -32,14 +32,15 @@ const Header = ({ onClose }: HeaderProps) => {
|
||||
<DialogDescription className="system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['plansCommon.title.description'], { ns: 'billing' })}
|
||||
</DialogDescription>
|
||||
<Button
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
className="absolute -right-4.5 bottom-[40.5px] z-10 size-9 rounded-full p-2"
|
||||
size="xl"
|
||||
className="absolute -right-4.5 bottom-[40.5px] z-10 rounded-full"
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onClose}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-close-line size-5" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { AccountSettingTab } from '@/app/components/header/account-setting/constants'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
@@ -173,15 +173,14 @@ export default function AccountSetting({
|
||||
return (
|
||||
<MenuDialog show onClose={onCancelAction}>
|
||||
<div className="fixed top-6 right-6 z-20 flex shrink-0 flex-col items-center">
|
||||
<Button
|
||||
<IconButton
|
||||
variant="tertiary"
|
||||
size="large"
|
||||
className="px-2"
|
||||
size="xl"
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onCancelAction}
|
||||
>
|
||||
<span className="i-ri-close-line size-5" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
|
||||
</div>
|
||||
<div className="flex h-screen w-full max-w-full pl-0 sm:pl-58">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { IntegrationSection } from './routes'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MenuDialog from '@/app/components/header/account-setting/menu-dialog'
|
||||
@@ -38,15 +38,14 @@ export default function IntegrationsSettingModal({
|
||||
onSwitchToMarketplace={handleSwitchToMarketplace}
|
||||
/>
|
||||
<div className="fixed top-6 right-6 flex shrink-0 flex-col items-center">
|
||||
<Button
|
||||
<IconButton
|
||||
variant="tertiary"
|
||||
size="large"
|
||||
className="px-2"
|
||||
size="xl"
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onCancel}
|
||||
>
|
||||
<span className="i-ri-close-line h-5 w-5" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ComponentProps, RefObject } from 'react'
|
||||
import type { StepByStepTourTaskId, StepByStepTourTaskView } from './types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { PopoverDescription, PopoverTitle } from '@langgenius/dify-ui/popover'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
@@ -80,16 +81,14 @@ export function FloatingChecklist({
|
||||
>
|
||||
{skipLabel}
|
||||
</Button>
|
||||
<Button
|
||||
<IconButton
|
||||
ref={closeButtonRef}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="size-6 px-0 text-text-tertiary hover:text-text-secondary"
|
||||
size="md"
|
||||
aria-label={minimizeLabel}
|
||||
onClick={onMinimize}
|
||||
>
|
||||
<span aria-hidden className="i-ri-arrow-left-down-line size-4" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
@@ -150,13 +151,12 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
|
||||
className="pr-10"
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
<IconButton
|
||||
size="lg"
|
||||
aria-label={t(($) => $[showPassword ? 'hidePassword' : 'showPassword'], {
|
||||
ns: 'login',
|
||||
})}
|
||||
className="mr-1 size-8 p-0 text-text-tertiary hover:text-text-secondary"
|
||||
className="mr-1"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
@@ -164,7 +164,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup }: MailAndP
|
||||
) : (
|
||||
<span className="i-ri-eye-line size-4" aria-hidden="true" />
|
||||
)}
|
||||
</Button>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
@@ -181,10 +181,13 @@
|
||||
"@typescript/native": "catalog:",
|
||||
"@vitejs/plugin-react": "catalog:",
|
||||
"@vitejs/plugin-rsc": "catalog:",
|
||||
"@vitest/browser": "catalog:",
|
||||
"@vitest/browser-playwright": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"agentation": "catalog:",
|
||||
"code-inspector-plugin": "catalog:",
|
||||
"happy-dom": "catalog:",
|
||||
"playwright": "catalog:",
|
||||
"postcss": "catalog:",
|
||||
"react-server-dom-webpack": "catalog:",
|
||||
"storybook": "catalog:",
|
||||
@@ -197,6 +200,7 @@
|
||||
"vite-plugin-inspect": "catalog:",
|
||||
"vite-plus": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-browser-react": "catalog:",
|
||||
"vitest-canvas-mock": "catalog:"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, lazyPlugins } from 'vite-plus'
|
||||
import { configDefaults } from 'vitest/config'
|
||||
import {
|
||||
createCodeInspectorPlugin,
|
||||
createForceInspectorClientInjectionPlugin,
|
||||
@@ -95,6 +96,7 @@ export default defineConfig(({ mode }) => {
|
||||
environment: 'happy-dom',
|
||||
globals: true,
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
exclude: [...configDefaults.exclude, '**/*.browser.spec.{ts,tsx}'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: isCI ? ['json', 'json-summary'] : ['text', 'json', 'json-summary'],
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite-plus'
|
||||
import { playwright } from 'vite-plus/test/browser-playwright'
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
'process.env': '{}',
|
||||
},
|
||||
plugins: [tailwindcss(), react()],
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['vite-plus/test/browser'],
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
setupFiles: ['./vitest.browser.setup.ts'],
|
||||
include: ['app/**/*.browser.spec.{ts,tsx}'],
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
headless: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { vi } from 'vitest'
|
||||
import './app/styles/globals.css'
|
||||
|
||||
document.documentElement.dataset.theme = 'light'
|
||||
|
||||
;(
|
||||
globalThis as typeof globalThis & { BASE_UI_ANIMATIONS_DISABLED: boolean }
|
||||
).BASE_UI_ANIMATIONS_DISABLED = true
|
||||
|
||||
vi.mock('react-i18next', async () => {
|
||||
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
|
||||
const { createReactI18nextMock } = await import('./test/i18n-mock')
|
||||
return {
|
||||
...actual,
|
||||
...createReactI18nextMock(),
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user