146 lines · 4.6 KB · TypeScript
RawDownload
"use client"; import type { ReactNode } from "react";import { Button } from "@/ui"; /** * The onboarding stage: one focal surface at a time, generous negative space, a ghost * glyph behind the content. Replaces the 18rem stepper rail + dense two-column layout — * progress is a quiet eyebrow ("STEP 1 OF 3") and three dots, nothing more. */ export interface SetupSurface {  readonly eyebrow: string;  readonly title: string;  readonly sub?: string;} export function SetupShell({  surfaceIndex,  surfaceCount,  surface,  onSkip,  children,}: {  surfaceIndex: number;  surfaceCount: number;  surface: SetupSurface;  onSkip: () => void;  children: ReactNode;}) {  return (    <div className="relative h-screen overflow-y-auto bg-(--bg)">      {/* Ghost glyph, Grok-style: enormous, barely-there. Fixed in its own clipping          layer so it neither scrolls with the content nor blocks scrolling. */}      <div aria-hidden className="pointer-events-none fixed inset-0 overflow-hidden">        <span          className="absolute -right-[10vw] -top-[22vh] select-none font-black leading-none tracking-tighter text-(--fg) opacity-[0.025]"          style={{ fontSize: "70vh" }}        >          LS        </span>      </div>       <div className="fixed right-5 top-5 z-10">        <Button variant="secondary" size="sm" onClick={onSkip}>          Skip for now        </Button>      </div>       <div className="relative mx-auto flex min-h-full w-full max-w-[640px] flex-col px-6 pb-16 pt-[14vh]">        <div className="mb-2 flex items-center gap-3">          <span className="text-[length:var(--fs-sm)] text-(--ui-muted)">            {surface.eyebrow}          </span>          <span className="flex items-center gap-1.5" aria-hidden>            {Array.from({ length: surfaceCount }, (_, index) => (              <span                key={index}                className={`h-1 w-1 rounded-full transition-colors ${                  index <= surfaceIndex ? "bg-(--ui-accent)" : "bg-(--ui-border)"                }`}              />            ))}          </span>        </div>        <h1 className="text-[length:var(--fs-2xl)] font-medium tracking-tight text-(--fg)">          {surface.title}        </h1>        {surface.sub ? (          <p className="mt-1.5 text-[length:var(--fs-sm)] text-(--ui-muted)">{surface.sub}</p>        ) : null}        <div className="mt-8">{children}</div>      </div>    </div>  );} /* ── checklist primitives (the Grok "get you up and running" card) ────────── */ export type ChecklistState = "pending" | "active" | "done"; export function ChecklistRow({  state,  title,  meta,  action,  children,}: {  state: ChecklistState;  title: string;  /** Right-aligned quiet detail shown when the row is not expanded. */  meta?: ReactNode;  /** Compact action shown in the row head while active. */  action?: ReactNode;  /** Expanded body, rendered only while active. */  children?: ReactNode;}) {  return (    <div      className={`rounded-[10px] border transition-colors ${        state === "active"          ? "border-(--ui-accent)/50 bg-(--ui-surface)/60"          : "border-(--ui-border) bg-(--ui-surface)/25"      }`}    >      <div className="flex items-center gap-3 px-4 py-3">        <ChecklistMark state={state} />        <span          className={`min-w-0 flex-1 truncate text-[length:var(--fs-md)] ${            state === "pending" ? "text-(--ui-muted)" : "text-(--fg)"          }`}        >          {title}        </span>        {state !== "active" && meta ? (          <span className="shrink-0 font-mono text-[length:var(--fs-xs)] text-(--ui-muted)">            {meta}          </span>        ) : null}        {state === "active" && action ? <span className="shrink-0">{action}</span> : null}      </div>      {state === "active" && children ? (        <div className="border-t border-(--ui-border)/60 px-4 py-4">{children}</div>      ) : null}    </div>  );} function ChecklistMark({ state }: { state: ChecklistState }) {  if (state === "done") {    return (      <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-(--ui-success) text-(--bg)">        <svg viewBox="0 0 12 12" className="h-3 w-3" fill="none" aria-hidden>          <path d="M2.5 6.5L5 9l4.5-6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />        </svg>      </span>    );  }  return (    <span      className={`h-5 w-5 shrink-0 rounded-full border-2 ${        state === "active" ? "border-(--ui-accent)" : "border-(--ui-border)"      }`}    />  );}