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 <[email protected]>
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') const menu = await openMenu('What Is My Dns')
await userEvent.click(menu.getByRole('menuitem', { name: 'Uninstall' })) await userEvent.click(menu.getByRole('menuitem', { name: 'Uninstall' }))
await userEvent.click( 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(spy.mock.calls[0][1]).toBe('What Is My Dns')
expect( expect(
@@ -484,7 +484,7 @@ describe('Apps: the store', () => {
await userEvent.click(row.getByRole('button', { name: 'Uninstall' })) await userEvent.click(row.getByRole('button', { name: 'Uninstall' }))
// The confirmation stacks over the store's dialog. // The confirmation stacks over the store's dialog.
await userEvent.click( await userEvent.click(
within(screen.getAllByRole('dialog').at(-1)!).getByRole('button', { name: 'Uninstall' }), within(screen.getAllByRole('alertdialog').at(-1)!).getByRole('button', { name: 'Uninstall' }),
) )
expect( expect(
await screen.findByText("DNS application 'NO DATA' was uninstalled successfully."), 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) const remove = vi.spyOn(zonelists, 'deleteDomain').mockResolvedValue(OK)
draw(NO_MODIFY) draw(NO_MODIFY)
await userEvent.click(await screen.findByRole('button', { name: 'Delete ads.example.com' })) 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'?") 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' })) await userEvent.click(within(dialog).getByRole('button', { name: 'Delete' }))
expect(remove).toHaveBeenCalledWith('blocked', 'T', 'ads.example.com') expect(remove).toHaveBeenCalledWith('blocked', 'T', 'ads.example.com')
@@ -117,7 +117,7 @@ describe('Rules', () => {
draw() draw()
await userEvent.click(await screen.findByRole('button', { name: 'Flush' })) await userEvent.click(await screen.findByRole('button', { name: 'Flush' }))
await userEvent.click(screen.getByRole('menuitem', { name: 'Allowed zones' })) 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 /* The sentences Allowed and Blocked said from their own headers, character for
@@ -140,7 +140,7 @@ describe('Rules', () => {
draw() draw()
await userEvent.click(await screen.findByRole('button', { name: 'Flush' })) await userEvent.click(await screen.findByRole('button', { name: 'Flush' }))
await userEvent.click(screen.getByRole('menuitem', { name: 'Blocked zones' })) 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 () => { it('flushing Allowed says so with upstream sentence', async () => {
@@ -149,7 +149,7 @@ describe('Rules', () => {
draw() draw()
await userEvent.click(await screen.findByRole('button', { name: 'Flush' })) await userEvent.click(await screen.findByRole('button', { name: 'Flush' }))
await userEvent.click(screen.getByRole('menuitem', { name: 'Allowed zones' })) 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(await screen.findByText('Allowed zone was flushed successfully.')).toBeInTheDocument()
expect(flush).toHaveBeenCalledWith('allowed', 'T') expect(flush).toHaveBeenCalledWith('allowed', 'T')
}) })
@@ -194,7 +194,7 @@ describe('Rules', () => {
await userEvent.click(screen.getByRole('button', { name: 'Tree' })) await userEvent.click(screen.getByRole('button', { name: 'Tree' }))
const tree = await screen.findByRole('region', { name: 'Records' }) const tree = await screen.findByRole('region', { name: 'Records' })
await userEvent.click(within(tree).getByRole('button', { name: 'Delete' })) 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)) await vi.waitFor(() => expect(read).toHaveBeenCalledTimes(4))
expect(read).toHaveBeenLastCalledWith('allowed', 'T', 'dev.cluster.test') expect(read).toHaveBeenLastCalledWith('allowed', 'T', 'dev.cluster.test')
await userEvent.click(screen.getByRole('button', { name: 'List' })) await userEvent.click(screen.getByRole('button', { name: 'List' }))
@@ -215,7 +215,7 @@ describe('Rules', () => {
await userEvent.click(screen.getByRole('button', { name: 'Tree' })) await userEvent.click(screen.getByRole('button', { name: 'Tree' }))
const tree = await screen.findByRole('region', { name: 'Records' }) const tree = await screen.findByRole('region', { name: 'Records' })
await userEvent.click(within(tree).getByRole('button', { name: 'Delete' })) 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.") const said = await screen.findAllByText("Blocked zone 'ads.test' was deleted successfully.")
expect(said).toHaveLength(1) expect(said).toHaveLength(1)
const bar = screen.getByRole('textbox', { name: 'Domain' }) 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('button', { name: 'Flush' }))
await userEvent.click(screen.getByRole('menuitem', { name: 'Blocked zones' })) 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.') await screen.findByText('Blocked zone was flushed successfully.')
expect(list).toHaveBeenLastCalledWith('blocked', 'T', '', undefined, 'dev.cluster.test') expect(list).toHaveBeenLastCalledWith('blocked', 'T', '', undefined, 'dev.cluster.test')
@@ -444,7 +444,7 @@ describe('Rules', () => {
const remove = vi.spyOn(zonelists, 'deleteDomain').mockResolvedValue(OK) const remove = vi.spyOn(zonelists, 'deleteDomain').mockResolvedValue(OK)
draw(NO_MODIFY) draw(NO_MODIFY)
await userEvent.click(await screen.findByRole('button', { name: 'Delete s.youtube.com' })) 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(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'?") 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' })) 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(screen.queryByRole('dialog')).toBeNull()
expect(remove).toHaveBeenCalledWith('blocked', 'T', 'ads.test') expect(remove).toHaveBeenCalledWith('blocked', 'T', 'ads.test')
await deleteRule('ok.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'?") 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. */ /* 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 { 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 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. 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. the validation error goes next to its field, and never in two places at once.
*/ */
.badRange { color: var(--dan); font-size: var(--t-2); } .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 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 { SectionHeader } from '../../ui/SectionHeader'
import { Loading } from '../../ui/Empty' import { Loading } from '../../ui/Empty'
import { Frame, FramePanel } from '@/components/reui/frame' import { Frame, FramePanel } from '@/components/reui/frame'
import { Input } from '@/components/ui/input'
import styles from './Dashboard.module.css' import styles from './Dashboard.module.css'
import { Body, Panel } from '../../ui/Panel' import { Body, Panel } from '../../ui/Panel'
import { Button } from '../../ui/Button' import { Button } from '../../ui/Button'
@@ -346,7 +347,7 @@ export function Dashboard({
<div className={styles.ownRange}> <div className={styles.ownRange}>
<label> <label>
Start Start
<input <Input
ref={startRef} ref={startRef}
type="date" type="date"
value={start} value={start}
@@ -357,7 +358,7 @@ export function Dashboard({
</label> </label>
<label> <label>
End End
<input <Input
ref={endRef} ref={endRef}
type="date" type="date"
value={end} value={end}
+2 -2
View File
@@ -249,7 +249,7 @@ describe('DHCP › Scopes', () => {
expect( expect(
screen.getByText("Are you sure you want to disable the DHCP scope 'Default'?"), screen.getByText("Are you sure you want to disable the DHCP scope 'Default'?"),
).toBeInTheDocument() ).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(spy).toHaveBeenCalledWith('t', 'Default', '')
expect(await screen.findByText('Scope Disabled!')).toBeInTheDocument() expect(await screen.findByText('Scope Disabled!')).toBeInTheDocument()
@@ -268,7 +268,7 @@ describe('DHCP › Scopes', () => {
expect( expect(
screen.getByText("Are you sure you want to delete the DHCP scope 'Default'?"), screen.getByText("Are you sure you want to delete the DHCP scope 'Default'?"),
).toBeInTheDocument() ).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(spy).toHaveBeenCalledWith('t', 'Default', '')
expect(await screen.findByText('Scope Deleted!')).toBeInTheDocument() 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: {} }) vi.spyOn(api, 'deleteDomain').mockResolvedValue({ kind: 'ok', data: {} })
render(<Lists list="blocked" token="T" nodes={NODES} clusterInitialised embedded />) render(<Lists list="blocked" token="T" nodes={NODES} clusterInitialised embedded />)
await userEvent.click(await screen.findByRole('button', { name: 'Delete' })) 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') expect(list).toHaveBeenLastCalledWith('blocked', 'T', 'test', 'up', 'dev.cluster.test')
}) })
@@ -89,7 +89,7 @@ describe('Lists embedded', () => {
const onChanged = vi.fn() const onChanged = vi.fn()
render(<Lists list="blocked" token="T" embedded onNotice={onNotice} onChanged={onChanged} />) render(<Lists list="blocked" token="T" embedded onNotice={onNotice} onChanged={onChanged} />)
await userEvent.click(await screen.findByRole('button', { name: 'Delete' })) 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({ expect(onNotice).toHaveBeenCalledWith({
type: 'success', title: 'Deleted!', text: "Blocked zone 'ads.test' was deleted successfully.", 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) { 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 })) await userEvent.click(within(dialog).getByRole('button', { name: label }))
} }
+3 -3
View File
@@ -158,7 +158,7 @@ describe('Logs › View Logs', () => {
expect( expect(
screen.getByText("Are you sure you want to permanently delete the log file '2026-08-26'?"), screen.getByText("Are you sure you want to permanently delete the log file '2026-08-26'?"),
).toBeInTheDocument() ).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(spy).toHaveBeenCalledWith('t', '2026-08-26', '')
expect(await screen.findByText('Log Deleted!')).toBeInTheDocument() 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?'), screen.getByText('Are you sure you want to permanently delete all log files?'),
).toBeInTheDocument() ).toBeInTheDocument()
await user.click( 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', '') 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?'), screen.getByText('Are you sure you want to permanently delete all stats files?'),
).toBeInTheDocument() ).toBeInTheDocument()
await user.click( 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') 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.getByLabelText('Select casa.test'))
await user.click(screen.getByRole('button', { name: 'Delete Zones' })) 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(() => { await waitFor(() => {
const call = spy.mock.calls.find((c) => c[0] === 'zones/delete') 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.getByLabelText('Select casa.test'))
await user.click(screen.getByRole('button', { name: 'Delete Zones' })) 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( expect(
await screen.findByText( await screen.findByText(
@@ -227,7 +227,7 @@ describe('records of a zone', () => {
await user.click(await screen.findByRole('menuitem', { name: 'Delete Record' })) 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() 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(() => { await waitFor(() => {
const call = spy.mock.calls.find((c) => String(c[0]).startsWith('zones/records/delete')) const call = spy.mock.calls.find((c) => String(c[0]).startsWith('zones/records/delete'))
expect(call![1]?.body).toMatchObject({ expect(call![1]?.body).toMatchObject({
+1 -1
View File
@@ -432,7 +432,7 @@ describe('SessionProvider', () => {
const user = await openMenu() const user = await openMenu()
await user.click(await screen.findByRole('menuitem', { name: 'Disable Update Notification' })) 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( expect(dialog).toHaveTextContent(
'Disabling update notification will prevent the Web Console from showing new update notification when you login.', '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 { useEffect, useRef, type ReactNode } from 'react'
import { Icon, type IconName } from './Icon' import { Icon, type IconName } from './Icon'
import { Alert as ReuiAlert } from '@/components/reui/alert' 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 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} <b className="font-medium">{title}</b> {children}
</div> </div>
{onDismiss && ( {onDismiss && (
<button <ReuiButton
type="button" 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" aria-label="Close"
onClick={onDismiss} onClick={onDismiss}
> >
<Icon name="close" size={14} /> <Icon name="close" size={14} className="size-3.5" />
</button> </ReuiButton>
)} )}
</ReuiAlert> </ReuiAlert>
) )
+36
View File
@@ -1,4 +1,14 @@
import { useState, type ReactNode } from 'react' 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 { Button } from './Button'
import { Check } from './Check' import { Check } from './Check'
import { Dialog } from './Dialog' 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 ( return (
<Dialog <Dialog
open={open} 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" 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 <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" aria-hidden="true"
> >
<Icon name={k.icon} size={18} /> <Icon name={k.icon} size={18} />
</span> </span>
<div className="flex min-w-0 flex-col gap-1"> <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"> <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> <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>} {k.hint != null && <Badge variant={k.tone ? BADGE[k.tone] : 'outline'}>{k.hint}</Badge>}