blob: 0ff914238948b787c2d0d03ed4a068f65a1893b0 [file]
import React, { forwardRef } from 'react';
import { Button as BaseButton } from '@base-ui/react/button';
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
import { Dialog as BaseDialog } from '@base-ui/react/dialog';
import { AlertDialog as BaseAlertDialog } from '@base-ui/react/alert-dialog';
import { Field as BaseField } from '@base-ui/react/field';
import { Progress as BaseProgress } from '@base-ui/react/progress';
import { Radio as BaseRadio } from '@base-ui/react/radio';
import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
import { Switch as BaseSwitch } from '@base-ui/react/switch';
import { Toggle as BaseToggle } from '@base-ui/react/toggle';
import { ToggleGroup as BaseToggleGroup } from '@base-ui/react/toggle-group';
import { Popover as BasePopover } from '@base-ui/react/popover';
import { Select as BaseSelect } from '@base-ui/react/select';
import { Separator as BaseSeparator } from '@base-ui/react/separator';
import { Check, ChevronDown, X } from './icons.js';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from './utils.js';
import { inputClasses } from './primitives/input.js';
import { useUiLocale } from './locale-context.js';
import { getSharedUiCopy } from './shared-ui-copy.js';
export { cn } from './utils.js';
export type PickerTriggerAppearance = 'field' | 'quiet';
const quietPickerTriggerClasses = [
'inline-flex shrink-0 items-center',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'disabled:pointer-events-none disabled:opacity-50',
].join(' ');
/**
* Picker triggers appear either as form fields or as quiet toolbar controls.
* The quiet variant intentionally carries only interaction states: its owning
* surface remains the single source of geometry and visual chrome.
*/
export function pickerTriggerClasses(appearance: PickerTriggerAppearance = 'field'): string {
return appearance === 'field' ? inputClasses : quietPickerTriggerClasses;
}
// === Base UI style-hook convention (#520 PR5 item 23) =========================
// Every Base UI wrapper in this file exposes `data-slot="<name>"` so CSS can
// target `[data-slot="..."]` (a stable hook that survives className drift);
// the shared primitives in `./primitives/` already do this (accordion / alert /
// badge / …), and new wrappers (Collapsible / Tooltip / NumberField / …) follow
// the same rule. Hand-written native elements (the legacy `Input` / `Textarea`
// below, and `Badge`) are out of this rule until they retire onto a Base UI
// primitive.
//
// Boolean state hooks adopt Base UI's NATIVE attribute-presence form —
// `[data-active]`, `[data-open]`, `[data-checked]`, `[data-selected]`,
// `[data-pressed]`, `[data-highlighted]`, `[data-disabled]` — NOT the
// attribute-value form `[data-active="true"]`. Maka's renderer CSS has zero
// state-attribute selectors today, so adopting Base UI's form breaks nothing
// and avoids maintaining an override layer. Per-component map:
// Tabs data-active (primitives/tabs.tsx)
// Select data-[highlighted] / data-[selected]
// Checkbox data-[checked] / data-[disabled]
// Switch data-[checked] / data-[disabled]
// Toggle data-[pressed] / data-[disabled]
// Radio data-[checked] / data-[disabled]
// Dialog data-[open] (open state on the root)
// Tooltip / Popover data-[open]
// Progress (no boolean state)
// CSS var hooks whitelisted for theming: `--anchor-*` (popups),
// `--available-*` (popup max-height), `--active-tab-*` (Tabs indicator).
// `className(state)` function form: deferred — add only when a migration in
// this PR actually needs state-based classes; do not pre-design it.
// ===========================================================================
export const buttonVariants = cva(
[
'inline-flex shrink-0 items-center justify-center gap-2 rounded-sm',
'transition-[background,color,border-color,box-shadow,opacity] duration-150 ease-[var(--ease-out-strong)]',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'disabled:pointer-events-none disabled:opacity-45 aria-disabled:cursor-not-allowed aria-disabled:opacity-45',
'[&_svg]:size-[var(--icon-size,1rem)] [&_svg]:shrink-0',
],
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground font-medium hover:bg-[oklch(from_var(--action)_calc(l_-_0.03)_c_h)] active:bg-[oklch(from_var(--action)_calc(l_-_0.06)_c_h)]',
secondary: 'border border-border bg-transparent text-foreground font-normal hover:bg-[var(--state-hover-bg)] active:bg-[var(--state-selected-bg)]',
ghost: 'bg-transparent text-foreground font-normal hover:bg-[var(--state-hover-bg)] active:bg-[var(--state-selected-bg)]',
destructive: 'bg-destructive text-destructive-foreground font-medium hover:bg-[oklch(from_var(--destructive)_calc(l_-_0.04)_c_h)] active:bg-[oklch(from_var(--destructive)_calc(l_-_0.08)_c_h)]',
quiet: 'bg-transparent text-foreground-secondary font-normal hover:bg-[var(--state-hover-bg)] hover:text-foreground active:bg-[var(--state-selected-bg)]',
},
size: {
sm: 'h-7 px-2 text-sm',
md: 'h-8 px-3 text-sm',
icon: 'h-8 w-8 px-0 text-sm',
'icon-sm': 'h-7 w-7 px-0 text-sm',
},
// #901 follow-up: circular affordance for round icon actions (composer
// "+" / send). #901 retired the consumer-layer pill CSS
// (maka-composer-send-button & friends) onto the shared Button but
// offered no governed replacement for the circle, silently squaring
// those buttons. The `shape` axis keeps the pill tier on the governed
// primitive instead of in per-surface CSS. `rounded-full` resolves to
// --radius-pill per the radius-converge contract.
shape: {
default: '',
pill: 'rounded-full',
},
},
compoundVariants: [
{
variant: ['secondary', 'ghost', 'quiet'],
class: 'aria-disabled:hover:bg-transparent aria-disabled:active:bg-transparent',
},
{
variant: 'quiet',
class: 'aria-disabled:hover:text-foreground-secondary',
},
{
variant: 'default',
class: 'aria-disabled:hover:bg-primary aria-disabled:active:bg-primary',
},
{
variant: 'destructive',
class: 'aria-disabled:hover:bg-destructive aria-disabled:active:bg-destructive',
},
],
defaultVariants: {
variant: 'default',
size: 'md',
shape: 'default',
},
},
);
interface ButtonProps
extends Omit<React.ComponentPropsWithoutRef<typeof BaseButton>, 'className'>,
VariantProps<typeof buttonVariants> {
className?: string;
}
export const Button = forwardRef<HTMLElement, ButtonProps>(function Button(
{ className, variant, size, shape, ...props },
ref,
) {
return (
<BaseButton
ref={ref}
className={cn(buttonVariants({ variant, size, shape }), className)}
data-slot="button"
{...props}
/>
);
});
// #520 item 22: Input, Textarea, inputClasses, bareFieldClasses retired onto
// packages/ui/src/primitives/input.tsx + primitives/textarea.tsx (Base UI
// Input + ported chrome, single element, no span wrapper). Re-exported from
// the barrel via index.ts; number-field imports inputClasses/bareFieldClasses
// from primitives/input.js.
export const Separator = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSeparator>>(function Separator(
{ className, orientation = 'horizontal', ...props },
ref,
) {
return (
<BaseSeparator
ref={ref}
orientation={orientation}
className={cn(
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
className,
)}
data-slot="separator"
{...props}
/>
);
});
export const Checkbox = forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof BaseCheckbox.Root>
>(function Checkbox({ className, ...props }, ref) {
return (
<BaseCheckbox.Root
ref={ref}
className={cn(
'relative inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[var(--radius-control)] border border-input bg-background text-foreground transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20',
'data-[checked]:border-control data-[checked]:bg-control data-[checked]:text-control-foreground',
'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50',
'pointer-coarse:after:absolute pointer-coarse:after:left-1/2 pointer-coarse:after:top-1/2 pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 pointer-coarse:after:-translate-x-1/2 pointer-coarse:after:-translate-y-1/2 pointer-coarse:after:content-[" "]',
className,
)}
data-slot="checkbox"
{...props}
>
<BaseCheckbox.Indicator className="grid place-items-center">
<Check size={11} aria-hidden="true" />
</BaseCheckbox.Indicator>
</BaseCheckbox.Root>
);
});
export const DialogRoot = BaseDialog.Root;
export const DialogClose = BaseDialog.Close;
export const AlertDialogRoot = BaseAlertDialog.Root;
// Shared modal shell. Dialog and AlertDialog differ only in their Base UI
// primitive family (Root/Portal/Backdrop/Popup/Close); the layout (backdrop
// class, popup class, Portal+Backdrop+Popup+optional Close structure) is
// identical. PR6 review P3.1: kills the AlertDialogBackdrop/Popup/Content
// triple that copied Dialog's, and lets ui-tsx-design-contract's
// the bare z-index/blur utility counts return to 1.
//
// `maka-dialog-backdrop` is a stable, style-free hook so tests and the
// real-window smoke diagnostic can select the dialog backdrop; Base UI
// renders only utility classes otherwise, which drift and aren't reliably
// selectable.
const MODAL_BACKDROP_CLASS = 'maka-dialog-backdrop fixed inset-0 z-40 bg-foreground/20 backdrop-blur-sm';
const MODAL_POPUP_CLASS =
'fixed left-1/2 top-1/2 z-50 grid max-h-[85dvh] w-[min(92vw,640px)] -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-xl bg-popover text-popover-foreground shadow-maka-panel';
type ModalContentProps = React.ComponentPropsWithoutRef<typeof BaseDialog.Popup> & { showClose?: boolean };
type ModalSlotPrefix = 'dialog' | 'alert-dialog';
type ModalBackdropProps = { className?: string; 'data-slot'?: string };
type ModalCloseProps = { className?: string; 'aria-label'?: string; 'data-slot'?: string; children?: React.ReactNode };
function createModalContent(primitives: {
Portal: React.ComponentType<{ children?: React.ReactNode }>;
Backdrop: React.ComponentType<ModalBackdropProps>;
Popup: React.ForwardRefExoticComponent<React.ComponentPropsWithoutRef<typeof BaseDialog.Popup> & React.RefAttributes<HTMLDivElement>>;
Close: React.ComponentType<ModalCloseProps>;
defaultShowClose: boolean;
slotPrefix: ModalSlotPrefix;
}) {
return forwardRef<HTMLDivElement, ModalContentProps>(function ModalContent(
{ className, children, showClose = primitives.defaultShowClose, ...props },
ref,
) {
const copy = getSharedUiCopy(useUiLocale()).primitives;
const { Portal, Backdrop, Popup, Close, slotPrefix } = primitives;
return (
<Portal>
<Backdrop className={MODAL_BACKDROP_CLASS} data-slot={`${slotPrefix}-backdrop`} />
<Popup ref={ref} className={cn(MODAL_POPUP_CLASS, className)} data-slot={`${slotPrefix}-popup`} {...props}>
{showClose && (
<Close
className={cn(buttonVariants({ variant: 'quiet', size: 'icon-sm' }), 'absolute right-3 top-3')}
aria-label={copy.close}
data-slot={`${slotPrefix}-close`}
>
<X aria-hidden="true" />
</Close>
)}
{children}
</Popup>
</Portal>
);
});
}
export const DialogContent = createModalContent({
Portal: BaseDialog.Portal,
Backdrop: BaseDialog.Backdrop,
Popup: BaseDialog.Popup,
Close: BaseDialog.Close,
defaultShowClose: true,
slotPrefix: 'dialog',
});
// AlertDialog — the alert variant locks modal + disables pointer dismissal,
// so confirmation dialogs require an explicit decision. Escape is NOT
// auto-disabled (Base UI alert-dialog still closes on Esc); callers that must
// not be Esc-dismissed intercept onOpenChange and cancel. PR6 (#520).
export const AlertDialogContent = createModalContent({
Portal: BaseAlertDialog.Portal,
Backdrop: BaseAlertDialog.Backdrop,
Popup: BaseAlertDialog.Popup,
Close: BaseAlertDialog.Close,
defaultShowClose: false,
slotPrefix: 'alert-dialog',
});
// Tabs: re-export the shared tab spec primitive (#499 P0-3). The tab spec
// (maka-tab class + underline/pill variants + neutral state tokens) lives in
// primitives/tabs.tsx. ui.tsx used to carry a second hand-rolled set (Base UI
// + bg-muted plate, no variant, dead data-[selected] active selectors — Base
// UI sets data-active) which plan-reminder-panel consumed, bypassing the spec.
// Re-exporting unifies on one primitive so every tab surface gets variant +
// maka-tab + the correct data-active attribute.
export { Tabs as TabsRoot, TabsList, TabsTab as TabsTrigger, TabsPanel } from './primitives/tabs.js';
export const SelectRoot = BaseSelect.Root;
export const SelectTrigger = forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof BaseSelect.Trigger> & { appearance?: PickerTriggerAppearance }
>(function SelectTrigger(
{ appearance = 'field', className, children, ...props },
ref,
) {
return (
<BaseSelect.Trigger
ref={ref}
className={cn(pickerTriggerClasses(appearance), 'justify-between', className)}
data-slot="select-trigger"
{...props}
>
{children}
<BaseSelect.Icon>
<ChevronDown size={14} aria-hidden="true" />
</BaseSelect.Icon>
</BaseSelect.Trigger>
);
});
export const SelectValue = BaseSelect.Value;
export const SelectPortal = BaseSelect.Portal;
/**
* The overlay layer belongs on the POSITIONER, not the popup.
*
* Base UI renders the popup `position: static` inside an absolutely
* positioned positioner. `z-index` has no effect on a static box, so the
* layer that used to sit on `SelectPopup` was inert; what actually kept
* settings selects above the modal was `.settingsSelectPositioner`
* (styles/settings/select.css), applied by hand at each call site. Any
* call site that forgot it portalled a popup that paints *below* the
* `.settingsModal` layer — invisible, and unclickable because the modal
* wins the hit-test. Settings → 通用 → 默认权限模式 shipped that way and
* read as "clicking does nothing at all".
*
* Carrying the layer here makes it structural: every Select consumer
* gets it by construction instead of by remembering a class name.
*/
export const SelectPositioner = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSelect.Positioner>>(function SelectPositioner(
{ className, ...props },
ref,
) {
return <BaseSelect.Positioner ref={ref} className={cn('z-[var(--z-overlay)]', className)} data-slot="select-positioner" {...props} />;
});
export const SelectPopup = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSelect.Popup>>(function SelectPopup(
{ className, ...props },
ref,
) {
return <BaseSelect.Popup ref={ref} className={cn('min-w-40 rounded-md bg-popover p-1 text-popover-foreground shadow-maka-panel', className)} data-slot="select-popup" {...props} />;
});
export const SelectGroup = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSelect.Group>>(function SelectGroup(
{ className, ...props },
ref,
) {
return <BaseSelect.Group ref={ref} className={cn('py-1', className)} data-slot="select-group" {...props} />;
});
export const SelectGroupLabel = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSelect.GroupLabel>>(function SelectGroupLabel(
{ className, ...props },
ref,
) {
return <BaseSelect.GroupLabel ref={ref} className={cn('px-2 py-1 text-xs font-medium text-foreground-secondary', className)} data-slot="select-group-label" {...props} />;
});
export const SelectSeparator = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSelect.Separator>>(function SelectSeparator(
{ className, ...props },
ref,
) {
return <BaseSelect.Separator ref={ref} className={cn('my-1 h-px bg-border', className)} data-slot="select-separator" {...props} />;
});
export const SelectItem = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseSelect.Item>>(function SelectItem(
{ className, children, ...props },
ref,
) {
return (
<BaseSelect.Item
ref={ref}
className={cn('grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-muted data-[selected]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', className)}
data-slot="select-item"
{...props}
>
<span className="flex h-4 w-4 items-center justify-center" aria-hidden="true">
<BaseSelect.ItemIndicator>
<Check size={13} aria-hidden="true" />
</BaseSelect.ItemIndicator>
</span>
<span className="min-w-0">
<BaseSelect.ItemText>{children}</BaseSelect.ItemText>
</span>
</BaseSelect.Item>
);
});
/**
* Popover — the anchored-surface primitive for pickers that are NOT a
* single-value list (Select already covers those). Added for the time
* picker, whose popup holds two independent columns and so has no single
* "selected item" for Select to own.
*
* The popup pins the same `--z-overlay` layer as `SelectPopup`: the
* Settings modal sits on its own layer, and a bare Tailwind z-utility
* would render the popup *beneath* the modal that triggered it — the bug
* fixed for Select in WAWQAQ msg `d3ea9a33`.
*/
export const PopoverRoot = BasePopover.Root;
export const PopoverTrigger = BasePopover.Trigger;
export const PopoverPortal = BasePopover.Portal;
/** Carries the overlay layer for the same reason `SelectPositioner` does —
* the popup below is `position: static`, so a z-index there is inert. */
export const PopoverPositioner = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BasePopover.Positioner>>(function PopoverPositioner(
{ className, ...props },
ref,
) {
return <BasePopover.Positioner ref={ref} className={cn('z-[var(--z-overlay)]', className)} data-slot="popover-positioner" {...props} />;
});
export const PopoverPopup = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BasePopover.Popup>>(function PopoverPopup(
{ className, ...props },
ref,
) {
return (
<BasePopover.Popup
ref={ref}
className={cn('rounded-md bg-popover p-1 text-popover-foreground shadow-maka-panel outline-none', className)}
data-slot="popover-popup"
{...props}
/>
);
});
// =============================================================
// Field + Form
// Base UI's Field handles label / control / description / error
// association automatically via aria-describedby and aria-invalid.
// =============================================================
export const FieldRoot = BaseField.Root;
export const FieldDescription = forwardRef<HTMLParagraphElement, React.ComponentPropsWithoutRef<typeof BaseField.Description>>(function FieldDescription(
{ className, ...props },
ref,
) {
return <BaseField.Description ref={ref} className={cn('text-xs text-foreground-secondary', className)} data-slot="field-description" {...props} />;
});
export const Label = forwardRef<HTMLLabelElement, React.ComponentPropsWithoutRef<typeof BaseField.Label>>(function Label(
{ className, ...props },
ref,
) {
return <BaseField.Label ref={ref} className={cn('text-sm font-medium text-foreground', className)} data-slot="label" {...props} />;
});
// =============================================================
// Switch
// =============================================================
export const Switch = forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof BaseSwitch.Root>
>(function Switch({ className, ...props }, ref) {
return (
<BaseSwitch.Root
ref={ref}
className={cn(
'relative inline-flex h-4.5 w-8 shrink-0 items-center rounded-full bg-foreground/16 transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
'data-[checked]:bg-control data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50',
'pointer-coarse:after:absolute pointer-coarse:after:left-1/2 pointer-coarse:after:top-1/2 pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 pointer-coarse:after:-translate-x-1/2 pointer-coarse:after:-translate-y-1/2 pointer-coarse:after:content-[" "]',
className,
)}
data-slot="switch"
{...props}
>
<BaseSwitch.Thumb className="block h-3.5 w-3.5 translate-x-0.5 rounded-full bg-background transition-transform duration-150 data-[checked]:translate-x-4 data-[checked]:bg-control-foreground" />
</BaseSwitch.Root>
);
});
// =============================================================
// Toggle + ToggleGroup
// =============================================================
export const Toggle = forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof BaseToggle>
>(function Toggle({ className, ...props }, ref) {
return (
<BaseToggle
ref={ref}
className={cn(
'inline-flex h-8 items-center justify-center rounded-sm bg-transparent px-2.5 text-sm font-medium text-foreground transition-colors',
'hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'data-[pressed]:bg-muted data-[pressed]:text-foreground',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
data-slot="toggle"
{...props}
/>
);
});
export const ToggleGroup = forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof BaseToggleGroup>
>(function ToggleGroup({ className, ...props }, ref) {
return (
<BaseToggleGroup
ref={ref}
className={cn('inline-flex items-center gap-1 rounded-md bg-muted p-1', className)}
data-slot="toggle-group"
{...props}
/>
);
});
// =============================================================
// RadioGroup + Radio
// =============================================================
export const RadioGroup = forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof BaseRadioGroup>
>(function RadioGroup({ className, ...props }, ref) {
return <BaseRadioGroup ref={ref} className={cn('grid gap-2', className)} data-slot="radio-group" {...props} />;
});
export const Radio = forwardRef<
HTMLButtonElement,
React.ComponentPropsWithoutRef<typeof BaseRadio.Root>
>(function Radio({ className, ...props }, ref) {
return (
<BaseRadio.Root
ref={ref}
className={cn(
'relative inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-input bg-background transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20',
'data-[checked]:border-control data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50',
'pointer-coarse:after:absolute pointer-coarse:after:left-1/2 pointer-coarse:after:top-1/2 pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 pointer-coarse:after:-translate-x-1/2 pointer-coarse:after:-translate-y-1/2 pointer-coarse:after:content-[" "]',
className,
)}
data-slot="radio"
{...props}
>
<BaseRadio.Indicator className="grid place-items-center">
<span className="block h-2 w-2 rounded-full bg-control" />
</BaseRadio.Indicator>
</BaseRadio.Root>
);
});
// =============================================================
// Progress
// =============================================================
export const Progress = forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof BaseProgress.Root>
>(function Progress({ className, ...props }, ref) {
return (
<BaseProgress.Root
ref={ref}
className={cn('relative h-2 w-full overflow-hidden rounded-full bg-muted', className)}
data-slot="progress"
{...props}
>
<BaseProgress.Track className="absolute inset-0 overflow-hidden">
<BaseProgress.Indicator className="block h-full origin-left bg-control transition-transform" />
</BaseProgress.Track>
</BaseProgress.Root>
);
});
// Toast — migrated to Base UI Toast in `packages/ui/src/toast.tsx`, exposed
// via the project's `useToast()` / `toast.confirm()` API (PR6 #520). The toast
// surface (Provider + manager + Viewport/Root/Title/Description/Action/Close)
// is Base UI; the confirm dialog + its queue stay hand-written (Base UI Toast
// has no confirm concept) and live in toast.tsx.