ReUI alignment: AlertDialog for destructive confirms, Input for date range, ghost Button for Alert close
CI / check (push) Successful in 3m35s
CI / image (push) Successful in 1m32s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Sonnet 5.5 committed 2026-10-30 10:55:18 +07:00
1 parent 7638939273
commit 59f882e58c
14 files changed
+71 -40

No files matched your search

+2 -2
View File
@@ -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."),
+8 -8
View File
@@ -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' }))
+1 -1
View File
@@ -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'?")
})
})
@@ -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
+3 -2
View File
@@ -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({
<div className={styles.ownRange}>
<label>
Start
<input
<Input
ref={startRef}
type="date"
value={start}
@@ -357,7 +358,7 @@ export function Dashboard({
</label>
<label>
End
<input
<Input
ref={endRef}
type="date"
value={end}
+2 -2
View File
@@ -249,7 +249,7 @@ describe('DHCP › Scopes', () => {
expect(
screen.getByText("Are you sure you want to disable the DHCP scope 'Default'?"),
).toBeInTheDocument()
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Disable' }))
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Disable' }))
expect(spy).toHaveBeenCalledWith('t', 'Default', '')
expect(await screen.findByText('Scope Disabled!')).toBeInTheDocument()
@@ -268,7 +268,7 @@ describe('DHCP › Scopes', () => {
expect(
screen.getByText("Are you sure you want to delete the DHCP scope 'Default'?"),
).toBeInTheDocument()
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete' }))
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete' }))
expect(spy).toHaveBeenCalledWith('t', 'Default', '')
expect(await screen.findByText('Scope Deleted!')).toBeInTheDocument()
+2 -2
View File
@@ -41,7 +41,7 @@ describe('Lists embedded', () => {
vi.spyOn(api, 'deleteDomain').mockResolvedValue({ kind: 'ok', data: {} })
render(<Lists list="blocked" token="T" nodes={NODES} clusterInitialised embedded />)
await userEvent.click(await screen.findByRole('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' }))
expect(list).toHaveBeenLastCalledWith('blocked', 'T', 'test', 'up', 'dev.cluster.test')
})
@@ -89,7 +89,7 @@ describe('Lists embedded', () => {
const onChanged = vi.fn()
render(<Lists list="blocked" token="T" embedded onNotice={onNotice} onChanged={onChanged} />)
await userEvent.click(await screen.findByRole('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' }))
expect(onNotice).toHaveBeenCalledWith({
type: 'success', title: 'Deleted!', text: "Blocked zone 'ads.test' was deleted successfully.",
})
+1 -1
View File
@@ -51,7 +51,7 @@ function withNode(...nodes: ListNode[]) {
}
async function confirm(label: string) {
const dialog = await screen.findByRole('dialog')
const dialog = await screen.findByRole('alertdialog')
await userEvent.click(within(dialog).getByRole('button', { name: label }))
}
+3 -3
View File
@@ -158,7 +158,7 @@ describe('Logs › View Logs', () => {
expect(
screen.getByText("Are you sure you want to permanently delete the log file '2026-08-26'?"),
).toBeInTheDocument()
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete' }))
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete' }))
expect(spy).toHaveBeenCalledWith('t', '2026-08-26', '')
expect(await screen.findByText('Log Deleted!')).toBeInTheDocument()
@@ -177,7 +177,7 @@ describe('Logs › View Logs', () => {
screen.getByText('Are you sure you want to permanently delete all log files?'),
).toBeInTheDocument()
await user.click(
within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete All Logs' }),
within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete All Logs' }),
)
expect(spy).toHaveBeenCalledWith('t', '')
@@ -197,7 +197,7 @@ describe('Logs › View Logs', () => {
screen.getByText('Are you sure you want to permanently delete all stats files?'),
).toBeInTheDocument()
await user.click(
within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete All Stats' }),
within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete All Stats' }),
)
expect(spy).toHaveBeenCalledWith('t')
+3 -3
View File
@@ -149,7 +149,7 @@ describe('zone list', () => {
await user.click(screen.getByLabelText('Select casa.test'))
await user.click(screen.getByRole('button', { name: 'Delete Zones' }))
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete' }))
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete' }))
await waitFor(() => {
const call = spy.mock.calls.find((c) => c[0] === 'zones/delete')
@@ -166,7 +166,7 @@ describe('zone list', () => {
await user.click(screen.getByLabelText('Select casa.test'))
await user.click(screen.getByRole('button', { name: 'Delete Zones' }))
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete' }))
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete' }))
expect(
await screen.findByText(
@@ -227,7 +227,7 @@ describe('records of a zone', () => {
await user.click(await screen.findByRole('menuitem', { name: 'Delete Record' }))
expect(await screen.findByText("Are you sure to permanently delete the A record 'www.casa.test'?")).toBeTruthy()
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Delete' }))
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', { name: 'Delete' }))
await waitFor(() => {
const call = spy.mock.calls.find((c) => String(c[0]).startsWith('zones/records/delete'))
expect(call![1]?.body).toMatchObject({
+1 -1
View File
@@ -432,7 +432,7 @@ describe('SessionProvider', () => {
const user = await openMenu()
await user.click(await screen.findByRole('menuitem', { name: 'Disable Update Notification' }))
const dialog = await screen.findByRole('dialog')
const dialog = await screen.findByRole('alertdialog')
expect(dialog).toHaveTextContent(
'Disabling update notification will prevent the Web Console from showing new update notification when you login.',
)
+7 -4
View File
@@ -1,6 +1,7 @@
import { useEffect, useRef, type ReactNode } from 'react'
import { Icon, type IconName } from './Icon'
import { Alert as ReuiAlert } from '@/components/reui/alert'
import { Button as ReuiButton } from '@/components/ui/button'
/*
A replica of the old console's `showAlert` (common.js:202-217): a type, a bold
@@ -79,14 +80,16 @@ export function Alert({
<b className="font-medium">{title}</b> {children}
</div>
{onDismiss && (
<button
<ReuiButton
type="button"
className="absolute top-2 right-2 inline-flex size-6 border-0 bg-transparent items-center justify-center rounded-md text-muted-foreground transition-colors outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
variant="ghost"
size="icon-xs"
className="absolute top-2 right-2 text-muted-foreground"
aria-label="Close"
onClick={onDismiss}
>
<Icon name="close" size={14} />
</button>
<Icon name="close" size={14} className="size-3.5" />
</ReuiButton>
)}
</ReuiAlert>
)
+36
View File
@@ -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 (
<AlertDialog open={open} onOpenChange={(o) => !o && !pending && onClose()}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
{/* `pre-wrap`: the Zones confirmations are multi-line. */}
<AlertDialogDescription render={<div />} style={{ whiteSpace: 'pre-wrap' }}>
{body}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive" disabled={pending} onClick={confirm}>
{label}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
return (
<Dialog
open={open}
+2 -2
View File
@@ -31,13 +31,13 @@ export function KpiStrip({ items, label }: { items: Kpi[]; label: string }) {
className="flex min-w-0 items-center gap-3 border-r border-b border-border px-[18px] py-3.5 last:border-r-0"
>
<span
className={`flex size-10 flex-none items-center justify-center rounded-lg border border-border bg-muted shadow-sm ${k.tone ? TEXT[k.tone] : 'text-foreground'}`}
className={`flex size-10 flex-none items-center justify-center rounded-lg border border-border bg-muted ${k.tone ? TEXT[k.tone] : 'text-foreground'}`}
aria-hidden="true"
>
<Icon name={k.icon} size={18} />
</span>
<div className="flex min-w-0 flex-col gap-1">
<div className="text-[13px] text-muted-foreground">{k.label}</div>
<div className="text-sm text-muted-foreground">{k.label}</div>
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span className={`text-2xl font-semibold tabular-nums ${k.tone ? TEXT[k.tone] : ''}`}>{k.value}</span>
{k.hint != null && <Badge variant={k.tone ? BADGE[k.tone] : 'outline'}>{k.hint}</Badge>}