"use client" /** * UtilityBarSchoolSwitcher — school/program (or site/location) scope picker for * the full-width `utility-bar` shell layout. * * Sits in the trailing identity cluster next to `UtilityUserMenu`. The trigger * is **avatar-only** on the utility bar so product chrome and the compact * breadcrumb keep the row; program / location still live in the tooltip and * menu (`showProgram` stays available for non-bar hosts). * * Menu body and state are shared with the sidebar switcher and the products * home — see `components/scope-switcher-menu.tsx`. A session whose scope is * fixed gets the same lockup with the names on it and no menu. * * `compact` matches the product switcher height on the compact shell (`h-8`). * School avatar stays `size-8` to match the profile avatar beside it. */ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, SHELL_IDENTITY_MENU_SURFACE_CLASS, } from "@/components/ui/dropdown-menu" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { utilityBarActionButtonClass } from "@/components/utility-bar-chrome" import { ScopeSwitcherMenuBody, useScopeSwitcher, } from "@/components/scope-switcher-menu" import { useProduct, type Product } from "@/contexts/product-context" import { cn } from "@/lib/utils" export function UtilityBarSchoolSwitcher({ compact = false, showProgram = true, }: { /** Smaller avatar + control height (compact shell or narrow bar). */ compact?: boolean /** * When false, avatar-only. Used on mobile / high-zoom so the product name and * action icons keep the row; program still lives in the tooltip and menu. */ showProgram?: boolean } = {}) { const { product } = useProduct() return ( ) } function ScopeAvatar({ parent, childIcon, }: { parent: { logo: string; initials: string } | null childIcon: string /** @deprecated Avatar is always size-8 to match profile. */ compact?: boolean }) { const size = "size-8" if (parent) { return ( ) } // No school chosen here yet, so there is no logo to stand for one. The // hierarchy's own glyph says what the control is for. return ( ) } function ProgramLabel({ name, compact, }: { name: string | null compact: boolean }) { if (!name) return null return ( {name} ) } function UtilityBarSchoolSwitcherInner({ product, compact, showProgram, }: { product: Product compact: boolean showProgram: boolean }) { const scope = useScopeSwitcher(product) const { parent, child } = scope const programName = child?.name ?? null // Nothing licensed in this product: no scope to name and none to switch to. The // page under the bar carries that message, so a control that opens an empty menu // would only add a place to press for nothing. if (scope.status === "none") return null if (scope.fixed && parent !== null && child !== null) { // Named lockup, no press. Tooltip is omitted for the same reason as before: // a focusable stop that does nothing is worse than putting the names on the // image + visible program label. return ( {showProgram ? : null} ) } return (