Skip to content

Commit 1a24c7e

Browse files
authored
Merge pull request #1986 from nextcloud-libraries/feat/quick-dialog
2 parents d3c8080 + 86e8589 commit 1a24c7e

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

l10n/messages.pot

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ msgid_plural "Choose %n files"
5050
msgstr[0] ""
5151
msgstr[1] ""
5252

53+
msgid "Confirm"
54+
msgstr ""
55+
5356
msgid "Continue"
5457
msgstr ""
5558

lib/dialogs.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { cleanup, findByRole, fireEvent, getByLabelText, getByRole } from '@testing-library/vue'
7+
import { afterEach, expect, test } from 'vitest'
8+
import { showConfirmation } from './dialogs.ts'
9+
10+
afterEach(cleanup)
11+
12+
test('Show confirmation dialog', async () => {
13+
const promise = showConfirmation({
14+
name: 'Dialog name',
15+
text: 'Dialog text',
16+
})
17+
18+
const dialog = await findByRole(document.documentElement, 'dialog')
19+
expect(dialog).toBeInstanceOf(HTMLElement)
20+
21+
expect(getByLabelText(document.documentElement, 'Dialog name')).toBe(dialog)
22+
expect(dialog.textContent).toContain('Dialog text')
23+
24+
const close = getByRole(dialog, 'button', { name: 'Close' })
25+
expect(close).toBeInstanceOf(HTMLElement)
26+
const confirm = getByRole(dialog, 'button', { name: 'Confirm' })
27+
expect(confirm).toBeInstanceOf(HTMLElement)
28+
await fireEvent(confirm, new MouseEvent('click', { bubbles: true }))
29+
expect(promise).resolves.toBe(true)
30+
})
31+
32+
test('show confirmation dialog with reject', async () => {
33+
const promise = showConfirmation({
34+
name: 'Dialog name',
35+
text: 'Dialog text',
36+
labelConfirm: 'My confirm',
37+
labelReject: 'My reject',
38+
})
39+
40+
const dialog = await findByRole(document.documentElement, 'dialog')
41+
const confirm = getByRole(dialog, 'button', { name: 'My confirm' })
42+
const reject = getByRole(dialog, 'button', { name: 'My reject' })
43+
expect(confirm).toBeInstanceOf(HTMLElement)
44+
expect(reject).toBeInstanceOf(HTMLElement)
45+
await fireEvent(reject, new MouseEvent('click', { bubbles: true }))
46+
expect(promise).resolves.toBe(false)
47+
})
48+
49+
test('show confirmation dialog and close', async () => {
50+
const promise = showConfirmation({
51+
name: 'Dialog name',
52+
text: 'Dialog text',
53+
})
54+
55+
const dialog = await findByRole(document.documentElement, 'dialog')
56+
const close = getByRole(dialog, 'button', { name: 'Close' })
57+
expect(close).toBeInstanceOf(HTMLElement)
58+
await fireEvent(close, new MouseEvent('click', { bubbles: true }))
59+
expect(promise).rejects.toThrowError('Dialog closed')
60+
})

lib/dialogs.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { IDialogButton, IDialogSeverity } from './components/types.ts'
77

88
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
99
import GenericDialog from './components/GenericDialog.vue'
10+
import { t } from './utils/l10n.ts'
1011
import { logger } from './utils/logger.ts'
1112

1213
export type * from './components/types.ts'
@@ -158,3 +159,57 @@ export class DialogBuilder {
158159
export function getDialogBuilder(name: string) {
159160
return new DialogBuilder(name)
160161
}
162+
163+
export interface ConfirmationDialogOptions {
164+
/** The name of the dialog (heading) */
165+
name: string
166+
/** The text of the dialog */
167+
text: string
168+
/** The text of the confirmation button */
169+
labelConfirm?: string
170+
/** The text of the reject button */
171+
labelReject?: string
172+
/** The severity */
173+
severity?: IDialogSeverity
174+
}
175+
176+
/**
177+
* Open a new confirmation dialog.
178+
* The dialog either resolves to true when the confirm-button was pressed,
179+
* or to false if the reject-button was pressed.
180+
*
181+
* @param options - Dialog options see `ConfirmationDialogOptions`
182+
*/
183+
export async function showConfirmation(options: ConfirmationDialogOptions): Promise<boolean> {
184+
options = {
185+
labelConfirm: t('Confirm'),
186+
...options,
187+
}
188+
189+
const { promise, resolve } = Promise.withResolvers<boolean>()
190+
const buttons: IDialogButton[] = [{
191+
label: options.labelConfirm!,
192+
variant: 'primary',
193+
callback() {
194+
resolve(true)
195+
},
196+
}]
197+
198+
if (options.labelReject) {
199+
buttons.unshift({
200+
label: options.labelReject,
201+
callback() {
202+
resolve(false)
203+
},
204+
})
205+
}
206+
207+
const dialog = new Dialog(
208+
options.name,
209+
options.text,
210+
buttons,
211+
options.severity,
212+
)
213+
await dialog.show()
214+
return promise
215+
}

0 commit comments

Comments
 (0)