"use client" /** * Scope trigger for a product card / marketing hero on the products home. * * Same menu as the utility bar and sidebar (`ScopeSwitcherMenuBody`) under a * wider trigger that names the scope instead of relying on a tooltip — there * is no surrounding product chrome here to give the avatar its meaning, so the * school and program have to be legible on the face of the control. * * Three triggers, same menu: * * - **`card`** — the bordered two-line control on a product card, where scope * is a standing property of the row and has the width to say so. * - **`inline`** — one quiet line under a hero's CTA. A card-shaped picker beside * the primary button competed with it: two filled controls, 60px against 44px, * neither obviously the thing to press. * - **`compact`** — `inline`'s text with the avatar dropped, for a caption * centered under an icon-grid cell (Launcher). The avatar duplicates the * mark already sitting above it in that context and, at cell width, the * two together left almost no room for the school name before it had to * truncate — the readable-but-unlabelled avatar felt like a stray control * rather than part of one caption. * * The menu opens **over** the trigger (select-style), matching its width and * `rounded-xl` radius so the two do not stack as separate rounded boxes. * * Writes through shared scope state, so the choice made here is the one the * product reports after Open. */ import * as React from "react" import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { ScopeSwitcherMenuBody, type ScopeSwitcherBag, } from "@/components/scope-switcher-menu" import { cn } from "@/lib/utils" export function ProductScopePicker({ scope, variant = "card", className, }: { scope: ScopeSwitcherBag variant?: "card" | "inline" | "compact" className?: string }) { const inline = variant === "inline" const compact = variant === "compact" // `inline` and `compact` both read as one line of "Child · Parent" text; // only the card variant needs the two-line stack the avatar sits beside. const oneLine = inline || compact const triggerRef = React.useRef(null) // Negative bottom offset so the panel top aligns with the trigger top. const [coverOffset, setCoverOffset] = React.useState(0) React.useLayoutEffect(() => { const el = triggerRef.current if (!el) return const update = () => setCoverOffset(-el.getBoundingClientRect().height) update() const ro = new ResizeObserver(update) ro.observe(el) return () => ro.disconnect() }, [variant, scope.child.name, scope.parent.name]) return (