mirror of
https://github.com/langgenius/dify.git
synced 2026-05-12 07:37:09 +08:00
refactor: enhance modal layouts and scrolling behavior across components (#36033)
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
parent
f1c4c1a5ff
commit
1082f488a1
@ -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
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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' })}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user