refactor: enhance modal layouts and scrolling behavior across components (#36033)

Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
Coding On Star 2026-05-11 14:16:56 +08:00 committed by GitHub
parent f1c4c1a5ff
commit 1082f488a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 91 additions and 62 deletions

View File

@ -71,6 +71,25 @@ describe('ConfigModal', () => {
}), undefined)
})
it('should keep scrolling inside the form body so scrollbars do not cover dialog corners', () => {
render(
<ConfigModal
isCreate
isShow
payload={createPayload({ label: 'Question' })}
onClose={vi.fn()}
onConfirm={vi.fn()}
/>,
)
const dialog = screen.getByRole('dialog')
const scrollArea = screen.getByTestId('config-modal-scroll-area')
expect(dialog).toHaveClass('overflow-hidden!')
expect(scrollArea).toHaveClass('overflow-y-auto')
expect(scrollArea).toHaveClass('overflow-x-hidden')
})
it('should block save when the label is missing', () => {
render(
<ConfigModal

View File

@ -148,12 +148,17 @@ const ConfigModal: FC<IConfigModalProps> = ({
onClose()
}}
>
<DialogContent className="overflow-hidden! border-none text-left align-middle">
<DialogTitle className="title-2xl-semi-bold text-text-primary">
<DialogContent className="flex max-h-[calc(100dvh-2rem)] flex-col overflow-hidden! border-none p-0! text-left align-middle">
<DialogTitle className="shrink-0 px-6 pt-6 title-2xl-semi-bold text-text-primary">
{t(`variableConfig.${isCreate ? 'addModalTitle' : 'editModalTitle'}`, { ns: 'appDebug' })}
</DialogTitle>
<div className="mb-8" ref={modalRef} tabIndex={-1}>
<div
ref={modalRef}
tabIndex={-1}
data-testid="config-modal-scroll-area"
className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto px-6 py-4 pb-8"
>
<ConfigModalFormFields
checkboxDefaultSelectValue={checkboxDefaultSelectValue}
isStringInput={isStringInput}
@ -172,10 +177,12 @@ const ConfigModal: FC<IConfigModalProps> = ({
t={t}
/>
</div>
<ModalFoot
onConfirm={handleConfirm}
onCancel={onClose}
/>
<div className="shrink-0 px-6 pt-2 pb-6">
<ModalFoot
onConfirm={handleConfirm}
onCancel={onClose}
/>
</div>
</DialogContent>
</Dialog>
)

View File

@ -131,7 +131,7 @@ const ParamsConfig = ({
}
}}
>
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none text-left align-middle sm:min-w-[528px]">
<DialogContent className="w-full max-w-[480px] border-none text-left align-middle sm:min-w-[528px]">
<ConfigContent
datasetConfigs={tempDataSetConfigs}

View File

@ -65,7 +65,7 @@ const ConfigParamModal: FC<Props> = ({ isShow, onHide: doHide, onSave, isInit, a
onHide()
}}
>
<DialogContent className="!mt-14 !w-[640px] !max-w-none overflow-hidden! border-none !p-6 text-left align-middle">
<DialogContent className="!mt-14 !w-[640px] !max-w-none border-none !p-6 text-left align-middle">
<div className="mb-2 title-2xl-semi-bold text-text-primary">
{t(`initSetup.${isInit ? 'title' : 'configTitle'}`, { ns: 'appAnnotation' })}

View File

@ -226,7 +226,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
return (
<Dialog open>
<DialogContent className="mt-14! w-[600px]! max-w-none! overflow-hidden! border-none p-6! text-left align-middle">
<DialogContent className="mt-14! w-[600px]! max-w-none! border-none p-6! text-left align-middle">
<div className="flex items-center justify-between">
<div className="title-2xl-semi-bold text-text-primary">{t('feature.moderation.modal.title', { ns: 'appDebug' })}</div>

View File

@ -61,7 +61,7 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({ data, onCance
}
return (
<Dialog open>
<DialogContent className="w-[640px]! max-w-none! overflow-hidden! border-none p-8! pb-6! text-left align-middle">
<DialogContent className="w-[640px]! max-w-none! border-none p-8! pb-6! text-left align-middle">
<div className="mb-2 text-xl font-semibold text-text-primary">
{data.name

View File

@ -187,7 +187,7 @@ const ModelLoadBalancingModal = ({ provider, configurateMethod, currentCustomCon
onClose?.()
}}
>
<DialogContent className="w-[640px] max-w-none overflow-hidden! border-none px-8 pt-8 text-left align-middle">
<DialogContent className="w-[640px] max-w-none border-none px-8 pt-8 text-left align-middle">
<DialogTitle className="title-2xl-semi-bold text-text-primary">
<div className="pb-3 font-semibold">
<div className="h-[30px]">

View File

@ -134,7 +134,7 @@ const MCPServerModal = ({
onHide()
}}
>
<DialogContent className="w-[calc(100vw-2rem)] max-w-[520px]! overflow-hidden! border-none p-0! text-left align-middle transition-all duration-100 ease-in">
<DialogContent className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-[520px]! flex-col overflow-hidden! border-none p-0! text-left align-middle transition-all duration-100 ease-in">
<button
type="button"
aria-label={t('operation.close', { ns: 'common' })}
@ -143,52 +143,55 @@ const MCPServerModal = ({
>
<RiCloseLine className="h-5 w-5 text-text-tertiary" aria-hidden="true" />
</button>
<div className="relative p-6 pb-3 title-2xl-semi-bold text-xl text-text-primary">
<div className="relative shrink-0 p-6 pr-12 pb-3 title-2xl-semi-bold text-xl wrap-break-word text-text-primary">
{!data ? t('mcp.server.modal.addTitle', { ns: 'tools' }) : t('mcp.server.modal.editTitle', { ns: 'tools' })}
</div>
<div className="space-y-5 px-6 py-3">
<div className="space-y-0.5">
<div className="flex h-6 items-center gap-1">
<div className="system-sm-medium text-text-secondary">{t('mcp.server.modal.description', { ns: 'tools' })}</div>
<div className="system-xs-regular text-text-destructive-secondary">*</div>
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
<div className="min-w-0 space-y-5 px-6 py-3">
<div className="space-y-0.5">
<div className="flex h-6 items-center gap-1">
<div className="system-sm-medium text-text-secondary">{t('mcp.server.modal.description', { ns: 'tools' })}</div>
<div className="system-xs-regular text-text-destructive-secondary">*</div>
</div>
<Textarea
className="h-[96px] resize-none"
value={description}
placeholder={t('mcp.server.modal.descriptionPlaceholder', { ns: 'tools' })}
onChange={e => setDescription(e.target.value)}
>
</Textarea>
</div>
<Textarea
className="h-[96px] resize-none"
value={description}
placeholder={t('mcp.server.modal.descriptionPlaceholder', { ns: 'tools' })}
onChange={e => setDescription(e.target.value)}
>
</Textarea>
{latestParams.length > 0 && (
<div className="min-w-0">
<div className="mb-1 flex items-center gap-2">
<div className="shrink-0 system-xs-medium-uppercase text-text-primary">{t('mcp.server.modal.parameters', { ns: 'tools' })}</div>
<Divider type="horizontal" className="m-0! h-px! grow bg-divider-subtle" />
</div>
<div className="mb-2 body-xs-regular text-text-tertiary">{t('mcp.server.modal.parametersTip', { ns: 'tools' })}</div>
<div className="min-w-0 space-y-3">
{latestParams.map((paramItem) => {
if (!paramItem.variable)
return null
const { variable } = paramItem
return (
<MCPServerParamItem
key={variable}
data={paramItem}
value={params[variable] || ''}
onChange={value => handleParamChange(variable, value)}
/>
)
})}
</div>
</div>
)}
</div>
{latestParams.length > 0 && (
<div>
<div className="mb-1 flex items-center gap-2">
<div className="shrink-0 system-xs-medium-uppercase text-text-primary">{t('mcp.server.modal.parameters', { ns: 'tools' })}</div>
<Divider type="horizontal" className="m-0! h-px! grow bg-divider-subtle" />
</div>
<div className="mb-2 body-xs-regular text-text-tertiary">{t('mcp.server.modal.parametersTip', { ns: 'tools' })}</div>
<div className="space-y-3">
{latestParams.map((paramItem) => {
if (!paramItem.variable)
return null
const { variable } = paramItem
return (
<MCPServerParamItem
key={variable}
data={paramItem}
value={params[variable] || ''}
onChange={value => handleParamChange(variable, value)}
/>
)
})}
</div>
</div>
)}
</div>
<div className="flex flex-row-reverse p-6 pt-5">
<Button disabled={!description || creating || updating} className="ml-2" variant="primary" onClick={submit}>{data ? t('mcp.modal.save', { ns: 'tools' }) : t('mcp.server.modal.confirm', { ns: 'tools' })}</Button>
<div className="flex shrink-0 flex-row-reverse flex-wrap gap-2 p-6 pt-5">
<Button disabled={!description || creating || updating} variant="primary" onClick={submit}>{data ? t('mcp.modal.save', { ns: 'tools' }) : t('mcp.server.modal.confirm', { ns: 'tools' })}</Button>
<Button onClick={onHide}>{t('mcp.modal.cancel', { ns: 'tools' })}</Button>
</div>
</DialogContent>

View File

@ -17,12 +17,12 @@ const MCPServerParamItem = ({
const { t } = useTranslation()
return (
<div className="space-y-0.5">
<div className="flex h-6 items-center gap-2">
<div className="system-xs-medium text-text-secondary">{data.label}</div>
<div className="min-w-0 space-y-0.5">
<div className="flex min-h-6 min-w-0 flex-wrap items-center gap-2">
<div className="max-w-full min-w-0 system-xs-medium wrap-break-word text-text-secondary">{data.label}</div>
<div className="system-xs-medium text-text-quaternary">·</div>
<div className="system-xs-medium text-text-secondary">{data.variable}</div>
<div className="system-xs-medium text-text-tertiary">{data.type}</div>
<div className="max-w-full min-w-0 system-xs-medium break-all text-text-secondary">{data.variable}</div>
<div className="max-w-full min-w-0 system-xs-medium wrap-break-word text-text-tertiary">{data.type}</div>
</div>
<Textarea
className="h-8 resize-none"

View File

@ -285,7 +285,7 @@ const MCPModal: FC<DuplicateAppModalProps> = ({
return (
<Dialog open={show}>
<DialogContent className="w-full max-w-[520px]! overflow-hidden! border-none p-6 text-left align-middle">
<DialogContent className="w-full max-w-[520px]! border-none p-6 text-left align-middle">
<MCPModalContent
key={formKey}
data={data}

View File

@ -122,7 +122,7 @@ const Authorization: FC<Props> = ({
onHide()
}}
>
<DialogContent className="overflow-hidden! border-none text-left align-middle">
<DialogContent className="border-none text-left align-middle">
<DialogTitle className="title-2xl-semi-bold text-text-primary">
{t(`${i18nPrefix}.authorization`, { ns: 'workflow' })}
</DialogTitle>

View File

@ -23,7 +23,7 @@ export function JsonSchemaConfigModal({
onClose()
}}
>
<DialogContent className="h-[800px] max-h-none w-full max-w-[960px] overflow-hidden! border-none p-0 text-left align-middle">
<DialogContent className="h-[calc(100dvh-32px)] max-h-[800px] w-full max-w-[960px] overflow-hidden! border-none p-0 text-left align-middle">
<JsonSchemaConfig
defaultSchema={defaultSchema}