diff --git a/src/screens/apps/Apps.test.tsx b/src/screens/apps/Apps.test.tsx index 03ff8e6..9e0b85f 100644 --- a/src/screens/apps/Apps.test.tsx +++ b/src/screens/apps/Apps.test.tsx @@ -217,7 +217,7 @@ describe('Apps: uninstalling', () => { const menu = await openMenu('What Is My Dns') await userEvent.click(menu.getByRole('menuitem', { name: 'Uninstall' })) await userEvent.click( - within(screen.getByRole('dialog')).getByRole('button', { name: 'Uninstall' }), + within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Uninstall' }), ) expect(spy.mock.calls[0][1]).toBe('What Is My Dns') expect( @@ -484,7 +484,7 @@ describe('Apps: the store', () => { await userEvent.click(row.getByRole('button', { name: 'Uninstall' })) // The confirmation stacks over the store's dialog. await userEvent.click( - within(screen.getAllByRole('dialog').at(-1)!).getByRole('button', { name: 'Uninstall' }), + within(screen.getAllByRole('alertdialog').at(-1)!).getByRole('button', { name: 'Uninstall' }), ) expect( await screen.findByText("DNS application 'NO DATA' was uninstalled successfully."), diff --git a/src/screens/blocking/Rules.test.tsx b/src/screens/blocking/Rules.test.tsx index 80ee531..16d9c5d 100644 --- a/src/screens/blocking/Rules.test.tsx +++ b/src/screens/blocking/Rules.test.tsx @@ -77,7 +77,7 @@ describe('Rules', () => { const remove = vi.spyOn(zonelists, 'deleteDomain').mockResolvedValue(OK) draw(NO_MODIFY) await userEvent.click(await screen.findByRole('button', { name: 'Delete ads.example.com' })) - const dialog = await screen.findByRole('dialog') + const dialog = await screen.findByRole('alertdialog') expect(dialog).toHaveTextContent("Are you sure you want to delete the blocked zone 'ads.example.com'?") await userEvent.click(within(dialog).getByRole('button', { name: 'Delete' })) expect(remove).toHaveBeenCalledWith('blocked', 'T', 'ads.example.com') @@ -117,7 +117,7 @@ describe('Rules', () => { draw() await userEvent.click(await screen.findByRole('button', { name: 'Flush' })) await userEvent.click(screen.getByRole('menuitem', { name: 'Allowed zones' })) - expect(await screen.findByRole('dialog')).toHaveTextContent('Are you sure you want to flush the entire Allowed zone?') + expect(await screen.findByRole('alertdialog')).toHaveTextContent('Are you sure you want to flush the entire Allowed zone?') }) /* The sentences Allowed and Blocked said from their own headers, character for @@ -140,7 +140,7 @@ describe('Rules', () => { draw() await userEvent.click(await screen.findByRole('button', { name: 'Flush' })) await userEvent.click(screen.getByRole('menuitem', { name: 'Blocked zones' })) - expect(await screen.findByRole('dialog')).toHaveTextContent('Are you sure you want to flush the entire Blocked zone?') + expect(await screen.findByRole('alertdialog')).toHaveTextContent('Are you sure you want to flush the entire Blocked zone?') }) it('flushing Allowed says so with upstream sentence', async () => { @@ -149,7 +149,7 @@ describe('Rules', () => { draw() await userEvent.click(await screen.findByRole('button', { name: 'Flush' })) await userEvent.click(screen.getByRole('menuitem', { name: 'Allowed zones' })) - await userEvent.click(within(await screen.findByRole('dialog')).getByRole('button', { name: 'Flush' })) + await userEvent.click(within(await screen.findByRole('alertdialog')).getByRole('button', { name: 'Flush' })) expect(await screen.findByText('Allowed zone was flushed successfully.')).toBeInTheDocument() expect(flush).toHaveBeenCalledWith('allowed', 'T') }) @@ -194,7 +194,7 @@ describe('Rules', () => { await userEvent.click(screen.getByRole('button', { name: 'Tree' })) const tree = await screen.findByRole('region', { name: 'Records' }) await userEvent.click(within(tree).getByRole('button', { name: 'Delete' })) - await userEvent.click(within(await screen.findByRole('dialog')).getByRole('button', { name: 'Delete' })) + await userEvent.click(within(await screen.findByRole('alertdialog')).getByRole('button', { name: 'Delete' })) await vi.waitFor(() => expect(read).toHaveBeenCalledTimes(4)) expect(read).toHaveBeenLastCalledWith('allowed', 'T', 'dev.cluster.test') await userEvent.click(screen.getByRole('button', { name: 'List' })) @@ -215,7 +215,7 @@ describe('Rules', () => { await userEvent.click(screen.getByRole('button', { name: 'Tree' })) const tree = await screen.findByRole('region', { name: 'Records' }) await userEvent.click(within(tree).getByRole('button', { name: 'Delete' })) - await userEvent.click(within(await screen.findByRole('dialog')).getByRole('button', { name: 'Delete' })) + await userEvent.click(within(await screen.findByRole('alertdialog')).getByRole('button', { name: 'Delete' })) const said = await screen.findAllByText("Blocked zone 'ads.test' was deleted successfully.") expect(said).toHaveLength(1) const bar = screen.getByRole('textbox', { name: 'Domain' }) @@ -398,7 +398,7 @@ describe('Rules', () => { await userEvent.click(screen.getByRole('button', { name: 'Flush' })) await userEvent.click(screen.getByRole('menuitem', { name: 'Blocked zones' })) - await userEvent.click(within(await screen.findByRole('dialog')).getByRole('button', { name: 'Flush' })) + await userEvent.click(within(await screen.findByRole('alertdialog')).getByRole('button', { name: 'Flush' })) await screen.findByText('Blocked zone was flushed successfully.') expect(list).toHaveBeenLastCalledWith('blocked', 'T', '', undefined, 'dev.cluster.test') @@ -444,7 +444,7 @@ describe('Rules', () => { const remove = vi.spyOn(zonelists, 'deleteDomain').mockResolvedValue(OK) draw(NO_MODIFY) await userEvent.click(await screen.findByRole('button', { name: 'Delete s.youtube.com' })) - const dialog = await screen.findByRole('dialog') + const dialog = await screen.findByRole('alertdialog') expect(within(dialog).getByRole('heading', { name: 'Delete Allowed Zone' })).toBeInTheDocument() expect(dialog).toHaveTextContent("Are you sure you want to delete the allowed zone 's.youtube.com'?") await userEvent.click(within(dialog).getByRole('button', { name: 'Delete' })) diff --git a/src/screens/blocking/Rules.undo.test.tsx b/src/screens/blocking/Rules.undo.test.tsx index 9138248..9e5fd9a 100644 --- a/src/screens/blocking/Rules.undo.test.tsx +++ b/src/screens/blocking/Rules.undo.test.tsx @@ -175,7 +175,7 @@ describe('Rules: Delete with Undo', () => { expect(screen.queryByRole('dialog')).toBeNull() expect(remove).toHaveBeenCalledWith('blocked', 'T', 'ads.test') await deleteRule('ok.test') - const dialog = await screen.findByRole('dialog') + const dialog = await screen.findByRole('alertdialog') expect(dialog).toHaveTextContent("Are you sure you want to delete the allowed zone 'ok.test'?") }) }) diff --git a/src/screens/dashboard/Dashboard.module.css b/src/screens/dashboard/Dashboard.module.css index bace37b..d00967d 100644 --- a/src/screens/dashboard/Dashboard.module.css +++ b/src/screens/dashboard/Dashboard.module.css @@ -248,14 +248,6 @@ At 720 the lists go to one column. /* The custom range: two dates and the button, on one line. */ .ownRange { display:flex; align-items:flex-end; gap: var(--s-7); flex-wrap:wrap; margin-bottom: var(--s-9); } .ownRange label { display:flex; flex-direction:column; gap: var(--s-3); font-size: var(--t-3); color:var(--mute); } -.ownRange input { - padding: var(--s-4) var(--s-6); - background: var(--pan2); - border: 1px solid var(--line-field); - border-radius: var(--r-2); - color: var(--ink); - font: 400 var(--t-4)/1.2 var(--font-mono); -} /* The `Queries` legend, in HTML. @@ -296,7 +288,6 @@ It goes here and not in the notice at the top because the design rule says so: the validation error goes next to its field, and never in two places at once. */ .badRange { color: var(--dan); font-size: var(--t-2); } -.ownRange input[aria-invalid] { border-color: var(--dan); } /* Beside a doughnut the legend is a small table: swatch, name, count and share, the diff --git a/src/screens/dashboard/Dashboard.tsx b/src/screens/dashboard/Dashboard.tsx index 8db10ad..66ba9b5 100644 --- a/src/screens/dashboard/Dashboard.tsx +++ b/src/screens/dashboard/Dashboard.tsx @@ -21,6 +21,7 @@ import { useDomainAction } from '../../lib/allow-block' import { SectionHeader } from '../../ui/SectionHeader' import { Loading } from '../../ui/Empty' import { Frame, FramePanel } from '@/components/reui/frame' +import { Input } from '@/components/ui/input' import styles from './Dashboard.module.css' import { Body, Panel } from '../../ui/Panel' import { Button } from '../../ui/Button' @@ -346,7 +347,7 @@ export function Dashboard({
{onDismiss && ( - + + )} ) diff --git a/src/ui/Confirm.tsx b/src/ui/Confirm.tsx index d98407d..618d0d2 100644 --- a/src/ui/Confirm.tsx +++ b/src/ui/Confirm.tsx @@ -1,4 +1,14 @@ import { useState, type ReactNode } from 'react' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog' import { Button } from './Button' import { Check } from './Check' import { Dialog } from './Dialog' @@ -64,6 +74,32 @@ export function Confirm({ }) } + /* A destructive confirm is ReUI's `AlertDialog`: it takes no outside click and + cannot be dismissed while the request runs. The one that only converts or + enables keeps the plain dialog. */ + if (variant === 'danger') { + const pending = busy || inProgress + return ( + !o && !pending && onClose()}> + + + {title} + {/* `pre-wrap`: the Zones confirmations are multi-line. */} + } style={{ whiteSpace: 'pre-wrap' }}> + {body} + + + + Cancel + + {label} + + + + + ) + } + return (
-
{k.label}
+
{k.label}
{k.value} {k.hint != null && {k.hint}}