mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 23:04:06 +00:00
fix: prevent confirm dialog buttons from being unreachable on mobile with long text (#8746)
## Summary Fix confirm dialog buttons becoming unreachable on mobile when text contains long unbreakable words (e.g. content-hashed filenames with 100+ characters). <img width="1080" height="2277" alt="image" src="https://github.com/user-attachments/assets/2f42afc9-c8ec-42aa-89d5-802dbaf788fd" /> ## Changes - **What**: Added `overflow-wrap: break-word` and `flex-wrap` to both confirm dialog systems so long words break properly and buttons wrap on narrow screens. - `ConfirmationDialogContent.vue`: Added `overflow-wrap: break-word` to the existing scoped style and `flex-wrap` to button row. - `ConfirmBody.vue`: Added `break-words` tailwind class. - `ConfirmFooter.vue`: Added `flex-wrap` to button section. ## Review Focus Minimal CSS-only fix across both dialog systems (legacy `dialogService.confirm()` and newer `showConfirmDialog()`). No behavioral changes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8746-fix-prevent-confirm-dialog-buttons-from-being-unreachable-on-mobile-with-long-text-3016d73d36508116bf55f0dc5cd89d0b) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
32
browser_tests/tests/confirmDialogTextWrap.spec.ts
Normal file
32
browser_tests/tests/confirmDialogTextWrap.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Confirm dialog text wrapping', { tag: ['@mobile'] }, () => {
|
||||
test('@mobile confirm dialog buttons are visible with long unbreakable text', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const longFilename = 'workflow_checkpoint_' + 'a'.repeat(200) + '.json'
|
||||
|
||||
await comfyPage.page.evaluate((msg) => {
|
||||
window
|
||||
.app!.extensionManager.dialog.confirm({
|
||||
title: 'Confirm',
|
||||
type: 'default',
|
||||
message: msg
|
||||
})
|
||||
.catch(() => {})
|
||||
}, longFilename)
|
||||
|
||||
const dialog = comfyPage.page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
const confirmButton = dialog.getByRole('button', { name: 'Confirm' })
|
||||
await expect(confirmButton).toBeVisible()
|
||||
await expect(confirmButton).toBeInViewport()
|
||||
|
||||
const cancelButton = dialog.getByRole('button', { name: 'Cancel' })
|
||||
await expect(cancelButton).toBeVisible()
|
||||
await expect(cancelButton).toBeInViewport()
|
||||
})
|
||||
})
|
||||
@@ -73,6 +73,10 @@ function getDialogPt(item: {
|
||||
<style>
|
||||
@reference '../../assets/css/style.css';
|
||||
|
||||
.global-dialog {
|
||||
max-width: calc(100vw - 1rem);
|
||||
}
|
||||
|
||||
.global-dialog .p-dialog-header {
|
||||
@apply p-2 2xl:p-[var(--p-dialog-header-padding)];
|
||||
@apply pb-0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col px-4 py-2 text-sm text-muted-foreground border-t border-border-default"
|
||||
class="flex flex-col break-words px-4 py-2 text-sm text-muted-foreground border-t border-border-default"
|
||||
>
|
||||
<p v-if="promptTextReal">
|
||||
{{ promptTextReal }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="w-full flex gap-2 justify-end px-2 pb-2">
|
||||
<section class="w-full flex flex-wrap gap-2 justify-end px-2 pb-2">
|
||||
<Button :disabled variant="textonly" autofocus @click="$emit('cancel')">
|
||||
{{ cancelTextX }}
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import ConfirmationDialogContent from './ConfirmationDialogContent.vue'
|
||||
|
||||
type Props = ComponentProps<typeof ConfirmationDialogContent>
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: {} },
|
||||
missingWarn: false,
|
||||
fallbackWarn: false
|
||||
})
|
||||
|
||||
describe('ConfirmationDialogContent', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
function mountComponent(props: Partial<Props> = {}) {
|
||||
return mount(ConfirmationDialogContent, {
|
||||
global: {
|
||||
plugins: [PrimeVue, i18n]
|
||||
},
|
||||
props: {
|
||||
message: 'Test message',
|
||||
type: 'default',
|
||||
onConfirm: vi.fn(),
|
||||
...props
|
||||
} as Props
|
||||
})
|
||||
}
|
||||
|
||||
it('renders long messages without breaking layout', () => {
|
||||
const longFilename =
|
||||
'workflow_checkpoint_' + 'a'.repeat(200) + '.safetensors'
|
||||
const wrapper = mountComponent({ message: longFilename })
|
||||
expect(wrapper.text()).toContain(longFilename)
|
||||
})
|
||||
})
|
||||
@@ -1,21 +1,24 @@
|
||||
<template>
|
||||
<section class="prompt-dialog-content m-2 mt-4 flex flex-col gap-6">
|
||||
<span>{{ message }}</span>
|
||||
<ul v-if="itemList?.length" class="m-0 flex flex-col gap-2 pl-4">
|
||||
<li v-for="item of itemList" :key="item">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
<Message
|
||||
v-if="hint"
|
||||
icon="pi pi-info-circle"
|
||||
severity="secondary"
|
||||
size="small"
|
||||
variant="simple"
|
||||
>
|
||||
{{ hint }}
|
||||
</Message>
|
||||
<div class="flex justify-end gap-4">
|
||||
<section class="m-2 mt-4 flex flex-col gap-6 whitespace-pre-wrap break-words">
|
||||
<div>
|
||||
<span>{{ message }}</span>
|
||||
<ul v-if="itemList?.length" class="m-0 mt-2 flex flex-col gap-2 pl-4">
|
||||
<li v-for="item of itemList" :key="item">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
<Message
|
||||
v-if="hint"
|
||||
class="mt-2"
|
||||
icon="pi pi-info-circle"
|
||||
severity="secondary"
|
||||
size="small"
|
||||
variant="simple"
|
||||
>
|
||||
{{ hint }}
|
||||
</Message>
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-wrap justify-end gap-4">
|
||||
<div
|
||||
v-if="type === 'overwriteBlueprint'"
|
||||
class="flex flex-col justify-start gap-1"
|
||||
@@ -151,9 +154,3 @@ const onConfirm = () => {
|
||||
useDialogStore().closeDialog()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.prompt-dialog-content {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user