ReUI alignment: AlertDialog for destructive confirms, Input for date range, ghost Button for Alert close
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7638939273
commit
59f882e58c
14 files changed
+71
-40
No files matched your search
@@ -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."),
|
||||
|
||||
@@ -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' }))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.",
|
||||
})
|
||||
|
||||
@@ -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 }))
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
@@ -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>}
|
||||
|
||||
Reference in new issue
Block a user