From eaab847464d525500130da18faada2906d286195 Mon Sep 17 00:00:00 2001 From: yyh Date: Tue, 14 Apr 2026 15:20:01 +0800 Subject: [PATCH 1/3] fix --- api/controllers/console/__init__.py | 1 + web/app/components/base/ui/dialog/index.tsx | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/api/controllers/console/__init__.py b/api/controllers/console/__init__.py index 98f87c9c4b..980e828945 100644 --- a/api/controllers/console/__init__.py +++ b/api/controllers/console/__init__.py @@ -203,6 +203,7 @@ __all__ = [ "saved_message", "setup", "site", + "socketio_workflow", "spec", "statistic", "tags", diff --git a/web/app/components/base/ui/dialog/index.tsx b/web/app/components/base/ui/dialog/index.tsx index 1b1424a469..94304c36e9 100644 --- a/web/app/components/base/ui/dialog/index.tsx +++ b/web/app/components/base/ui/dialog/index.tsx @@ -83,16 +83,9 @@ export function DialogContent({ overlayClassName, backdropProps, }: DialogContentProps) { - const backdropContentProps = backdropProps - ? (({ className: _className, ...rest }) => rest)(backdropProps) - : {} - return ( - + Date: Tue, 14 Apr 2026 15:30:24 +0800 Subject: [PATCH 2/3] Revert "refactor(web): migrate content-dialog to base/ui/dialog primitives" This reverts commit 1049cbaa19fd763578b73c6ca6c1d11144580a79. --- .../__tests__/app-info-detail-panel.spec.tsx | 24 ++-- .../app-info/app-info-detail-panel.tsx | 133 +++++++++--------- .../app-sidebar/app-info/app-operations.tsx | 22 +-- .../content-dialog/__tests__/index.spec.tsx | 59 ++++++++ .../base/content-dialog/index.stories.tsx | 119 ++++++++++++++++ .../components/base/content-dialog/index.tsx | 40 ++++++ web/app/components/base/ui/dialog/index.tsx | 47 ++----- web/eslint-suppressions.json | 18 +++ 8 files changed, 334 insertions(+), 128 deletions(-) create mode 100644 web/app/components/base/content-dialog/__tests__/index.spec.tsx create mode 100644 web/app/components/base/content-dialog/index.stories.tsx create mode 100644 web/app/components/base/content-dialog/index.tsx diff --git a/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx b/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx index 44966f0ebe..3082eb3789 100644 --- a/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx +++ b/web/app/components/app-sidebar/app-info/__tests__/app-info-detail-panel.spec.tsx @@ -11,26 +11,22 @@ vi.mock('../../../base/app-icon', () => ({ ), })) -vi.mock('@/app/components/base/ui/dialog', () => ({ - Dialog: ({ open, onOpenChange, children }: { - open: boolean - onOpenChange: (open: boolean) => void +vi.mock('@/app/components/base/content-dialog', () => ({ + default: ({ show, onClose, children, className }: { + show: boolean + onClose: () => void children: React.ReactNode + className?: string }) => ( - open + show ? ( -
- +
+ {children}
) : null ), - DialogPortal: ({ children }: { children: React.ReactNode }) => <>{children}, - DialogBackdrop: () =>
, - DialogPopup: ({ children, className }: { children: React.ReactNode, className?: string }) => ( -
{children}
- ), })) vi.mock('@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view', () => ({ @@ -100,12 +96,12 @@ describe('AppInfoDetailPanel', () => { describe('Rendering', () => { it('should not render when show is false', () => { render() - expect(screen.queryByTestId('detail-drawer')).not.toBeInTheDocument() + expect(screen.queryByTestId('content-dialog')).not.toBeInTheDocument() }) it('should render dialog when show is true', () => { render() - expect(screen.getByTestId('detail-drawer')).toBeInTheDocument() + expect(screen.getByTestId('content-dialog')).toBeInTheDocument() }) it('should display app name', () => { diff --git a/web/app/components/app-sidebar/app-info/app-info-detail-panel.tsx b/web/app/components/app-sidebar/app-info/app-info-detail-panel.tsx index 93e7b4c586..4aacc0cdb1 100644 --- a/web/app/components/app-sidebar/app-info/app-info-detail-panel.tsx +++ b/web/app/components/app-sidebar/app-info/app-info-detail-panel.tsx @@ -1,17 +1,20 @@ import type { Operation } from './app-operations' import type { AppInfoModalType } from './use-app-info-actions' import type { App, AppSSO } from '@/types/app' +import { + RiDeleteBinLine, + RiEditLine, + RiExchange2Line, + RiFileCopy2Line, + RiFileDownloadLine, + RiFileUploadLine, +} from '@remixicon/react' import * as React from 'react' -import { useCallback, useMemo } from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view' import Button from '@/app/components/base/button' -import { - Dialog, - DialogBackdrop, - DialogPopup, - DialogPortal, -} from '@/app/components/base/ui/dialog' +import ContentDialog from '@/app/components/base/content-dialog' import { AppModeEnum } from '@/types/app' import AppIcon from '../../base/app-icon' import { getAppModeLabel } from './app-mode-labels' @@ -34,28 +37,23 @@ const AppInfoDetailPanel = ({ }: AppInfoDetailPanelProps) => { const { t } = useTranslation() - const handleOpenChange = useCallback((open: boolean) => { - if (!open) - onClose() - }, [onClose]) - const primaryOperations = useMemo(() => [ { id: 'edit', title: t('editApp', { ns: 'app' }), - icon: , + icon: , onClick: () => openModal('edit'), }, { id: 'duplicate', title: t('duplicate', { ns: 'app' }), - icon: , + icon: , onClick: () => openModal('duplicate'), }, { id: 'export', title: t('export', { ns: 'app' }), - icon: , + icon: , onClick: exportCheck, }, ], [t, openModal, exportCheck]) @@ -65,7 +63,7 @@ const AppInfoDetailPanel = ({ ? [{ id: 'import', title: t('common.importDSL', { ns: 'workflow' }), - icon: , + icon: , onClick: () => openModal('importDSL'), }] : [], @@ -79,7 +77,7 @@ const AppInfoDetailPanel = ({ { id: 'delete', title: t('operation.delete', { ns: 'common' }), - icon: , + icon: , onClick: () => openModal('delete'), }, ], [appDetail.mode, t, openModal]) @@ -90,64 +88,63 @@ const AppInfoDetailPanel = ({ return { id: 'switch', title: t('switch', { ns: 'app' }), - icon: , + icon: , onClick: () => openModal('switch'), } }, [appDetail.mode, t, openModal]) return ( - - - - -
-
- -
-
{appDetail.name}
-
- {getAppModeLabel(appDetail.mode, t)} -
-
-
- {appDetail.description && ( -
- {appDetail.description} -
- )} - -
- +
+
+ - {switchOperation && ( -
- +
+
{appDetail.name}
+
+ {getAppModeLabel(appDetail.mode, t)}
- )} - - -
+
+
+ {appDetail.description && ( +
+ {appDetail.description} +
+ )} + + + + {switchOperation && ( +
+ +
+ )} + ) } diff --git a/web/app/components/app-sidebar/app-info/app-operations.tsx b/web/app/components/app-sidebar/app-info/app-operations.tsx index b2a9bc97c8..e3cf233fea 100644 --- a/web/app/components/app-sidebar/app-info/app-operations.tsx +++ b/web/app/components/app-sidebar/app-info/app-operations.tsx @@ -1,8 +1,8 @@ import type { JSX } from 'react' +import { RiMoreLine } from '@remixicon/react' import { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem' export type Operation = { @@ -133,8 +133,8 @@ const AppOperations = ({ className="gap-px" tabIndex={-1} > - {cloneElement(operation.icon, { className: cn(operation.icon.props.className, 'h-3.5 w-3.5 text-components-button-secondary-text') })} - + {cloneElement(operation.icon, { className: 'h-3.5 w-3.5 text-components-button-secondary-text' })} + {operation.title} @@ -146,8 +146,8 @@ const AppOperations = ({ className="gap-px" tabIndex={-1} > - - + + {t('operation.more', { ns: 'common' })} @@ -162,8 +162,8 @@ const AppOperations = ({ className="gap-px" onClick={operation.onClick} > - {cloneElement(operation.icon, { className: cn(operation.icon.props.className, 'h-3.5 w-3.5 text-components-button-secondary-text') })} - + {cloneElement(operation.icon, { className: 'h-3.5 w-3.5 text-components-button-secondary-text' })} + {operation.title} @@ -181,8 +181,8 @@ const AppOperations = ({ variant="secondary" className="gap-px" > - - + + {t('operation.more', { ns: 'common' })} @@ -199,8 +199,8 @@ const AppOperations = ({ className="flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover" onClick={item.onClick} > - {cloneElement(item.icon, { className: cn(item.icon.props.className, 'h-4 w-4 text-text-tertiary') })} - {item.title} + {cloneElement(item.icon, { className: 'h-4 w-4 text-text-tertiary' })} + {item.title} ))} diff --git a/web/app/components/base/content-dialog/__tests__/index.spec.tsx b/web/app/components/base/content-dialog/__tests__/index.spec.tsx new file mode 100644 index 0000000000..e987d306a1 --- /dev/null +++ b/web/app/components/base/content-dialog/__tests__/index.spec.tsx @@ -0,0 +1,59 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import ContentDialog from '../index' + +describe('ContentDialog', () => { + it('renders children when show is true', async () => { + render( + +
Dialog body
+
, + ) + + await screen.findByText('Dialog body') + expect(screen.getByText('Dialog body')).toBeInTheDocument() + + const backdrop = document.querySelector('.bg-app-detail-overlay-bg') + expect(backdrop).toBeTruthy() + }) + + it('does not render children when show is false', () => { + render( + +
Hidden content
+
, + ) + + expect(screen.queryByText('Hidden content')).toBeNull() + expect(document.querySelector('.bg-app-detail-overlay-bg')).toBeNull() + }) + + it('calls onClose when backdrop is clicked', async () => { + const onClose = vi.fn() + render( + +
Body
+
, + ) + + const user = userEvent.setup() + const backdrop = document.querySelector('.bg-app-detail-overlay-bg') as HTMLElement | null + expect(backdrop).toBeTruthy() + + await user.click(backdrop!) + expect(onClose).toHaveBeenCalledTimes(1) + }) + + it('applies provided className to the content panel', () => { + render( + +
Panel content
+
, + ) + + const contentPanel = document.querySelector('.bg-app-detail-bg') as HTMLElement | null + expect(contentPanel).toBeTruthy() + expect(contentPanel?.className).toContain('my-panel-class') + expect(screen.getByText('Panel content')).toBeInTheDocument() + }) +}) diff --git a/web/app/components/base/content-dialog/index.stories.tsx b/web/app/components/base/content-dialog/index.stories.tsx new file mode 100644 index 0000000000..8ddd5c667d --- /dev/null +++ b/web/app/components/base/content-dialog/index.stories.tsx @@ -0,0 +1,119 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite' +import { useEffect, useState } from 'react' +import ContentDialog from '.' + +type Props = React.ComponentProps + +const meta = { + title: 'Base/Feedback/ContentDialog', + component: ContentDialog, + parameters: { + layout: 'fullscreen', + docs: { + description: { + component: 'Sliding panel overlay used in the app detail view. Includes dimmed backdrop and animated entrance/exit transitions.', + }, + }, + }, + tags: ['autodocs'], + argTypes: { + className: { + control: 'text', + description: 'Additional classes applied to the sliding panel container.', + }, + show: { + control: 'boolean', + description: 'Controls visibility of the dialog.', + }, + onClose: { + control: false, + description: 'Invoked when the overlay/backdrop is clicked.', + }, + children: { + control: false, + table: { disable: true }, + }, + }, + args: { + show: false, + children: null, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +const DemoWrapper = (props: Props) => { + const [open, setOpen] = useState(props.show) + + useEffect(() => { + setOpen(props.show) + }, [props.show]) + + return ( +
+
+ +
+ + { + props.onClose?.() + setOpen(false) + }} + > +
+

Plan summary

+

+ Use this area to present rich content for the selected run, configuration details, or + any supporting context. +

+
+ Scrollable placeholder content. Add domain-specific information, activity logs, or + editors in the real application. +
+
+ + +
+
+
+
+ ) +} + +export const Default: Story = { + args: { + children: null, + }, + render: args => , +} + +export const NarrowPanel: Story = { + render: args => , + args: { + className: 'max-w-[420px]', + children: null, + }, + parameters: { + docs: { + description: { + story: 'Applies a custom width class to show the dialog as a narrower information panel.', + }, + }, + }, +} diff --git a/web/app/components/base/content-dialog/index.tsx b/web/app/components/base/content-dialog/index.tsx new file mode 100644 index 0000000000..e12365b691 --- /dev/null +++ b/web/app/components/base/content-dialog/index.tsx @@ -0,0 +1,40 @@ +import type { ReactNode } from 'react' +import { Transition, TransitionChild } from '@headlessui/react' +import { cn } from '@/utils/classnames' + +type ContentDialogProps = { + className?: string + show: boolean + onClose?: () => void + children: ReactNode +} + +const ContentDialog = ({ + className, + show, + onClose, + children, +}: ContentDialogProps) => { + return ( + + +
+ + + +
+ {children} +
+
+ + ) +} + +export default ContentDialog diff --git a/web/app/components/base/ui/dialog/index.tsx b/web/app/components/base/ui/dialog/index.tsx index 94304c36e9..5dcebfcac2 100644 --- a/web/app/components/base/ui/dialog/index.tsx +++ b/web/app/components/base/ui/dialog/index.tsx @@ -39,37 +39,6 @@ export function DialogCloseButton({ ) } -export function DialogBackdrop({ - className, - ...props -}: React.ComponentPropsWithoutRef) { - return ( - - ) -} - -export function DialogPopup({ - className, - children, - ...props -}: React.ComponentPropsWithoutRef) { - return ( - - {children} - - ) -} - type DialogContentProps = { children: React.ReactNode className?: string @@ -85,16 +54,24 @@ export function DialogContent({ }: DialogContentProps) { return ( - - + {children} - + ) } diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index e177f4526c..61e648da41 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -314,6 +314,11 @@ "count": 1 } }, + "app/components/app-sidebar/app-info/app-info-detail-panel.tsx": { + "tailwindcss/enforce-consistent-class-order": { + "count": 5 + } + }, "app/components/app-sidebar/app-info/app-info-trigger.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 2 @@ -325,6 +330,9 @@ }, "react/set-state-in-effect": { "count": 4 + }, + "tailwindcss/enforce-consistent-class-order": { + "count": 5 } }, "app/components/app-sidebar/app-sidebar-dropdown.tsx": { @@ -1977,6 +1985,16 @@ "count": 5 } }, + "app/components/base/content-dialog/index.stories.tsx": { + "react/set-state-in-effect": { + "count": 1 + } + }, + "app/components/base/content-dialog/index.tsx": { + "tailwindcss/enforce-consistent-class-order": { + "count": 2 + } + }, "app/components/base/copy-feedback/index.tsx": { "no-restricted-imports": { "count": 1 From e4389b3f586739e2122430e1574103ea9592a481 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:35:16 +0000 Subject: [PATCH 3/3] [autofix.ci] apply automated fixes --- web/eslint-suppressions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 61e648da41..00a0284f57 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -1992,7 +1992,7 @@ }, "app/components/base/content-dialog/index.tsx": { "tailwindcss/enforce-consistent-class-order": { - "count": 2 + "count": 1 } }, "app/components/base/copy-feedback/index.tsx": {