import type { ReactNode } from "react"; import { Box, Text, TextAttributes, useUiHost } from "../../ui"; import { useViewport } from "../../react/input"; import { blendHex } from "../../theme/colors"; import { useThemeColors } from "../../theme/theme-context"; import { t } from "../../i18n"; import { Button, ListView, type ListViewItem } from "../ui"; import type { ButtonProps } from "../ui/button"; import { TITLEBAR_OVERLAY_HEIGHT_PX } from "../layout/titlebar-overlay"; /** * Spacing for the DOM card, in px. The card is a form the user fills in, not * pane chrome, so it does not sit on the terminal cell grid: an 8px rhythm with * one larger break before the footer. */ export const ONBOARDING_DESKTOP = { padding: "26px 28px 24px", radius: 10, afterProgress: 22, afterTitle: 6, afterHeader: 20, beforeFooter: 24, buttonHeight: "28px", } as const; export function OnboardingModal({ children, width = 66, height = 18, desktopWidth, }: { children: ReactNode; /** Terminal card width in cells. */ width?: number; /** Terminal card height in rows; the DOM card sizes to its content. */ height?: number; /** CSS width for the DOM card when a step needs more than the default surface. */ desktopWidth?: string; }) { const colors = useThemeColors(); const desktop = useUiHost().kind === "desktop-web"; const viewport = useViewport(); if (desktop) { return ( {children} ); } const overlayHeight = Math.max(1, viewport.height - 1); const availableWidth = Math.max(1, viewport.width - 4); const availableHeight = Math.max(1, overlayHeight - 1); const cardWidth = Math.min(Math.max(42, Math.min(width, availableWidth)), availableWidth); const cardHeight = Math.min(Math.max(10, Math.min(height, availableHeight)), availableHeight); const top = Math.max(0, Math.floor((overlayHeight - cardHeight) / 2)); const left = Math.max(0, Math.floor((viewport.width - cardWidth) / 2)); return ( {children} ); } export function OnboardingCoach({ step, title, children, actions, }: { step: string; title: string; children: ReactNode; actions: ReactNode; }) { const colors = useThemeColors(); const desktop = useUiHost().kind === "desktop-web"; const viewport = useViewport(); if (desktop) { return ( {step} {title} {children} {actions} ); } const availableWidth = Math.max(1, viewport.width - 4); const cardWidth = Math.min(Math.max(40, Math.min(54, availableWidth)), availableWidth); const cardHeight = Math.min(12, Math.max(1, viewport.height - 3)); const right = viewport.width - cardWidth >= 2 ? 2 : 0; return ( {step} {title} {children} {actions} ); } export function OnboardingTitle({ step, title, titlePrefix, titleSuffix, description, }: { step?: string; title: string; titlePrefix?: ReactNode; /** Short qualifier after the title, e.g. why a struck-through anchor price differs. */ titleSuffix?: string; description?: string; }) { const colors = useThemeColors(); const desktop = useUiHost().kind === "desktop-web"; if (desktop) { return ( {step ? ( {step} ) : null} {titlePrefix} {title} {titleSuffix ? {titleSuffix} : null} {description ? ( {description} ) : null} ); } return ( {step ? ( {step} ) : null} {titlePrefix} {title} {titleSuffix ? {titleSuffix} : null} {description ? ( <> {description} ) : null} ); } export type OnboardingSectionId = "portfolio" | "cloud" | "pro"; export function OnboardingHeader({ active, available, onNavigate, onDismiss, dismissShortcut, dismissing = false, dismissDisabled = false, showDismiss = true, }: { active?: OnboardingSectionId; available?: Partial>; onNavigate?: (section: OnboardingSectionId) => void; onDismiss: () => void; /** The key the wizard binds to Skip setup, shown on the button. */ dismissShortcut?: string; dismissing?: boolean; dismissDisabled?: boolean; showDismiss?: boolean; }) { const colors = useThemeColors(); const desktop = useUiHost().kind === "desktop-web"; const sections: Array<{ id: OnboardingSectionId; label: string }> = [ { id: "portfolio", label: t("Portfolio") }, { id: "cloud", label: t("Gloom Cloud") }, { id: "pro", label: t("Pro") }, ]; if (!desktop) { return ( {t("GLOOMBERB SETUP")} {showDismiss ? (