refactor(desktop): export DialogCloseButton for the boot-failure overlay

The overlay carried a byte-for-byte copy of DialogContent's close button.
This commit is contained in:
Brooklyn Nicholson
2026-09-28 11:35:24 -05:00
committed by brooklyn!
parent 1b8569f41f
commit 6007b3ce4f
2 changed files with 28 additions and 48 deletions

View File

@@ -3,6 +3,7 @@ import { Dialog as DialogPrimitive } from 'radix-ui'
import { type ComponentProps, lazy, type ReactNode, Suspense, useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { DialogCloseButton } from '@/components/ui/dialog'
import { DialogPortalContainerContext } from '@/components/ui/dialog-portal-context'
import { ErrorIcon } from '@/components/ui/error-state'
import { Loader } from '@/components/ui/loader'
@@ -12,17 +13,7 @@ import { useI18n } from '@/i18n'
import { reestablishCloudAgentSession } from '@/lib/cloud-agent-session'
import { DESKTOP_DOCS_URL } from '@/lib/docs'
import { openExternalLink } from '@/lib/external-link'
import {
ChevronLeft,
ExternalLink,
FileText,
Loader2,
LogIn,
RefreshCw,
SlidersHorizontal,
Wrench,
X
} from '@/lib/icons'
import { ChevronLeft, ExternalLink, FileText, Loader2, LogIn, RefreshCw, SlidersHorizontal, Wrench } from '@/lib/icons'
import { $desktopBoot } from '@/store/boot'
import { notify, notifyError } from '@/store/notifications'
import { $desktopOnboarding } from '@/store/onboarding'
@@ -54,25 +45,6 @@ type RecoveryView = 'connect' | 'recovery'
// re-establish the remote session. The detection + copy helpers live in
// ./boot-failure-reauth so they're unit-testable without a React render.
// Radix Close routes through the modal's onOpenChange, same path as Escape.
function DismissControl() {
const { t } = useI18n()
return (
<DialogPrimitive.Close asChild data-slot="dialog-close-button">
<Button
aria-label={t.common.close}
className="absolute right-2.5 top-2.5 z-20 text-(--ui-text-tertiary) hover:bg-(--chrome-action-hover) hover:text-foreground"
size="icon-xs"
variant="ghost"
>
<X className="size-4" />
<span className="sr-only">{t.common.close}</span>
</Button>
</DialogPrimitive.Close>
)
}
// Recovery surface for a hard boot failure (gateway never came up, backend
// exited during startup, bootstrap latched, …). Without this the app shell
// renders dead — "gateway offline", no composer, only a toast — with no way
@@ -504,7 +476,7 @@ export function BootFailureOverlay() {
return (
<BootFailureModal onDismiss={dismiss} title={copy.gatewaySettings}>
<div className="relative flex max-h-[86vh] w-full max-w-[46rem] flex-col overflow-hidden rounded-xl border border-(--stroke-nous) bg-(--ui-chat-bubble-background) shadow-nous">
<DismissControl />
<DialogCloseButton />
{/* Subtle back affordance (projects/overlay idiom): muted → foreground
on hover, no divider. */}
<button
@@ -528,7 +500,7 @@ export function BootFailureOverlay() {
return (
<BootFailureModal onDismiss={dismiss}>
<div className="relative w-full max-w-[40rem] overflow-hidden rounded-xl border border-(--stroke-nous) bg-(--ui-chat-bubble-background) shadow-nous">
<DismissControl />
<DialogCloseButton />
<div className="flex items-start gap-3 px-5 py-4 pr-12">
<ErrorIcon className="mt-0.5" size="1.25rem" />
<div>

View File

@@ -69,6 +69,27 @@ export function preventCloseButtonAutoFocus(event: Event) {
event.preventDefault()
}
// The dialog's top-right X. Radix Close routes through the modal's
// onOpenChange, same path as Escape. Exported for bespoke Radix shells (the
// boot-failure overlay) that can't use DialogContent but want the same X.
function DialogCloseButton() {
const { t } = useI18n()
return (
<DialogPrimitive.Close asChild data-slot="dialog-close-button">
<Button
aria-label={t.common.close}
className="absolute right-2.5 top-2.5 z-20 text-(--ui-text-tertiary) hover:bg-(--chrome-action-hover) hover:text-foreground"
size="icon-xs"
variant="ghost"
>
<X className="size-4" />
<span className="sr-only">{t.common.close}</span>
</Button>
</DialogPrimitive.Close>
)
}
function DialogContent({
className,
bodyClassName,
@@ -105,8 +126,6 @@ function DialogContent({
banner?: React.ReactNode
bannerTone?: DialogBannerTone
}) {
const { t } = useI18n()
const widthClass = fitContent ? 'w-auto max-w-[92vw]' : 'w-full max-w-lg'
// Publish the dialog's content node so popovers (Select / Popover /
@@ -133,20 +152,8 @@ function DialogContent({
// an input) is what most dialogs want. Dialogs with no input should pass
// `onOpenAutoFocus={preventCloseButtonAutoFocus}` explicitly instead.
// No tip on the X — the glyph is the label. Keep aria-label / sr-only for a11y.
const closeButton = showCloseButton ? (
<DialogPrimitive.Close asChild data-slot="dialog-close-button">
<Button
aria-label={t.common.close}
className="absolute right-2.5 top-2.5 z-20 text-(--ui-text-tertiary) hover:bg-(--chrome-action-hover) hover:text-foreground"
size="icon-xs"
variant="ghost"
>
<X className="size-4" />
<span className="sr-only">{t.common.close}</span>
</Button>
</DialogPrimitive.Close>
) : null
// No tip on the X — the glyph is the label (DialogCloseButton keeps aria-label / sr-only).
const closeButton = showCloseButton ? <DialogCloseButton /> : null
// With a banner, the border can't live on the scroll/clip box (it would draw a
// line around the banner too). The white body keeps its own bottom radius and
@@ -314,6 +321,7 @@ function DialogDescription({ className, ...props }: React.ComponentProps<typeof
export {
Dialog,
DialogClose,
DialogCloseButton,
DialogContent,
DialogDescription,
DialogFooter,