// Shared UI primitives. The install wizard set the visual baseline; this file // exposes the primary button, secondary button, input, error message, and // copy button so the auth and in-app surfaces can match. import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; import { ClipboardDocumentCheckIcon, ClipboardIcon, } from "@heroicons/react/16/solid"; import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { createContext, useContext, useState, type ComponentProps, type ReactNode, } from "react"; export const INPUT_CLASS = "block w-full rounded-lg bg-panel-alt px-3.5 py-2.5 text-base text-fg outline-1 -outline-offset-1 outline-white/10 placeholder:text-fg-muted focus:outline-2 focus:-outline-offset-1 focus:outline-teal-500 sm:text-sm"; export const PRIMARY_BUTTON_CLASS = "inline-flex items-center justify-center gap-2 rounded-lg bg-teal-500 px-4 py-2 text-sm font-medium text-on-primary transition-colors hover:bg-teal-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-500 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-teal-500"; export const SECONDARY_BUTTON_CLASS = "inline-flex items-center justify-center gap-2 rounded-lg bg-transparent px-3.5 py-2 text-sm font-medium text-fg-2 outline-1 -outline-offset-1 outline-white/10 hover:bg-overlay hover:text-fg focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-teal-500 disabled:cursor-not-allowed disabled:opacity-60"; const DANGER_BUTTON_CLASS = "inline-flex items-center justify-center gap-2 rounded-lg bg-coral px-4 py-2 text-sm font-medium text-on-primary transition-colors hover:bg-coral/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coral disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-coral"; export const COMPACT_SECONDARY_BUTTON_CLASS = "rounded-md border border-line bg-overlay px-2.5 py-1 text-xs text-fg-2 transition-colors hover:bg-overlay-strong hover:text-fg disabled:cursor-not-allowed disabled:opacity-50"; export function ErrorMessage({ message }: { message: string }) { return (

{message}

); } export function CopyButton({ value, label, className = "", }: { value: string; label: string; className?: string; }) { const [copied, setCopied] = useState(false); return ( ); } export function ConfirmDialog({ open, title, description, confirmLabel, pendingLabel, cancelLabel = "Cancel", pending = false, onConfirm, onCancel, }: { open: boolean; title: string; description: ReactNode; confirmLabel: string; pendingLabel?: string; cancelLabel?: string; pending?: boolean; onConfirm: () => void; onCancel: () => void; }) { return ( { if (!pending) onCancel(); }} className="relative z-50" > ); } const TOOLTIP_DELAY_DURATION = 200; const TOOLTIP_SKIP_DELAY_DURATION = 300; const HasTooltipProviderContext = createContext(false); type TooltipProviderProps = ComponentProps; function canUseOverlayDom() { return typeof window !== "undefined" && typeof document !== "undefined"; } export function TooltipProvider({ delayDuration = TOOLTIP_DELAY_DURATION, skipDelayDuration = TOOLTIP_SKIP_DELAY_DURATION, children, ...props }: TooltipProviderProps) { if (!canUseOverlayDom()) { return <>{children}; } return ( {children} ); } export function Tooltip({ label, children, }: { label: ReactNode; children: ReactNode; }) { // Tests and isolated mounts may render without an ancestor // ; Radix throws in that case, so supply a local provider. const hasProvider = useContext(HasTooltipProviderContext); if (!canUseOverlayDom()) { return {children}; } const root = ( {children} {label} ); return hasProvider ? root : {root}; } /** * Hover-triggered rich popover. Unlike `Tooltip`, it anchors below the trigger, * stays within the viewport, and allows multi-line wrapping content. The * `content` node is mounted only while open, so consumers may fetch lazily. */ export function HoverCard({ content, children, className = "inline-flex", openDelay = 0, }: { content: ReactNode; children: ReactNode; className?: string; openDelay?: number; }) { if (!canUseOverlayDom()) { return {children}; } return ( {children} {content} ); } export function PopoverHeader({ children }: { children: ReactNode }) { return (
{children}
); } export function PopoverRows({ children }: { children: ReactNode }) { return
{children}
; } export function PopoverRow({ label, children }: { label: string; children: ReactNode }) { return ( <>
{label}
{children}
); }