{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "account-switcher",
  "title": "Account Switcher",
  "author": "ilinxa",
  "description": "Popover account and context switcher — active label trigger, switchable context list, and a footer slot for create or request actions.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "popover",
    "separator"
  ],
  "files": [
    {
      "path": "src/registry/components/navigation/account-switcher/account-switcher.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useId, useMemo } from \"react\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\nimport { useControllableState } from \"./hooks/use-controllable-state\";\nimport { enforceUniqueKeys } from \"./lib/enforce-unique-keys\";\nimport { resolveActiveItem } from \"./lib/resolve-active-item\";\nimport { EmptyPlaceholder } from \"./parts/empty-placeholder\";\nimport { SwitcherItemRow } from \"./parts/switcher-item-row\";\nimport {\n  SwitcherTriggerContent,\n  composeSwitcherTriggerAriaLabel,\n  switcherTriggerClassName,\n} from \"./parts/switcher-trigger\";\nimport type { AccountSwitcherProps, SwitcherItem } from \"./types\";\n\nconst DEFAULT_ARIA_LABEL = \"Switch account context\";\n\n/**\n * Account Switcher — popover-with-switchable-items primitive.\n *\n * See [`docs/procomps/account-switcher-procomp/`](../../../../../docs/procomps/account-switcher-procomp/)\n * for description + plan + guide. Locks L1–L14, all PQs at default per\n * 2026-05-23 GATE 2 close.\n */\nexport function AccountSwitcher(props: AccountSwitcherProps) {\n  const {\n    items,\n    activeKey,\n    onSelect,\n    fallbackActiveItem,\n    footerSlot,\n    isCollapsed = false,\n    collapsedPopoverSide = \"right\",\n    \"aria-label\": ariaLabel = DEFAULT_ARIA_LABEL,\n    ariaCurrent = \"true\",\n    open: openProp,\n    defaultOpen,\n    onOpenChange,\n    className,\n  } = props;\n\n  const listboxId = useId();\n\n  const [open, setOpen] = useControllableState<boolean>({\n    value: openProp,\n    defaultValue: defaultOpen ?? false,\n    onChange: onOpenChange,\n    componentName: \"account-switcher\",\n    valuePropName: \"open\",\n  });\n\n  // L3 + Q2 — dev-warn + strip duplicates once per items reference change.\n  const dedupedItems = useMemo(() => enforceUniqueKeys(items), [items]);\n\n  const activeResolution = useMemo(\n    () => resolveActiveItem(dedupedItems, activeKey, fallbackActiveItem),\n    [dedupedItems, activeKey, fallbackActiveItem],\n  );\n\n  const activeItem = activeResolution.kind === \"empty\" ? null : activeResolution.item;\n\n  // F-cross-13 guard at the shadcn-primitive boundary. The consumer's\n  // `onOpenChange` is shielded — `useControllableState` always calls it with\n  // the validated `boolean`. Plan §10.\n  const handlePrimitiveOpenChange = useCallback(\n    (next: unknown) => {\n      if (typeof next !== \"boolean\") return;\n      setOpen(next);\n    },\n    [setOpen],\n  );\n\n  // L6 — active-item clicks close the popover but do NOT fire onSelect.\n  const handleItemClick = useCallback(\n    (item: SwitcherItem) => {\n      if (item.key === activeItem?.key) {\n        setOpen(false);\n        return;\n      }\n      onSelect(item);\n      setOpen(false);\n    },\n    [activeItem?.key, onSelect, setOpen],\n  );\n\n  // Empty-state branch (Q1)\n  if (activeResolution.kind === \"empty\") {\n    return (\n      <EmptyPlaceholder\n        ariaLabel={ariaLabel}\n        isCollapsed={isCollapsed}\n        className={className}\n      />\n    );\n  }\n\n  const popoverSide = isCollapsed ? collapsedPopoverSide : \"bottom\";\n  const popoverWidthStyle = isCollapsed\n    ? undefined\n    : ({ width: \"var(--radix-popover-trigger-width)\" } as React.CSSProperties);\n\n  return (\n    <Popover open={open} onOpenChange={handlePrimitiveOpenChange}>\n      {/* v0.1.1 (F-cross-13 path-b): no `asChild` — Base UI's PopoverTrigger\n          rejects it. The trigger IS the combobox button: both backends render\n          a native <button> and pass native DOM props straight through. */}\n      <PopoverTrigger\n        type=\"button\"\n        role=\"combobox\"\n        aria-haspopup=\"listbox\"\n        aria-expanded={open}\n        aria-controls={listboxId}\n        aria-label={composeSwitcherTriggerAriaLabel(ariaLabel, activeItem)}\n        className={switcherTriggerClassName({ isCollapsed, className })}\n      >\n        <SwitcherTriggerContent activeItem={activeItem} isCollapsed={isCollapsed} />\n      </PopoverTrigger>\n      <PopoverContent\n        side={popoverSide}\n        align=\"start\"\n        sideOffset={4}\n        // No collisionPadding — Radix-only prop; Base UI's PopoverContent\n        // rejects it (F-cross-13). Both backends still collision-flip.\n        style={popoverWidthStyle}\n        className={cn(\n          \"w-auto min-w-56 gap-0 p-1\",\n          // override the primitive's baked-in w-72; Radix exposes\n          // --radix-popover-trigger-width, Base UI --anchor-width (F-cross-13)\n          !isCollapsed && \"w-(--radix-popover-trigger-width,var(--anchor-width))\",\n        )}\n      >\n        <ul id={listboxId} role=\"listbox\" aria-label={ariaLabel} className=\"flex flex-col gap-0.5\">\n          {dedupedItems.map((item) => (\n            <SwitcherItemRow\n              key={item.key}\n              item={item}\n              isActive={item.key === activeItem?.key}\n              ariaCurrent={ariaCurrent}\n              onSelect={() => handleItemClick(item)}\n            />\n          ))}\n        </ul>\n        {footerSlot ? (\n          <>\n            <Separator className=\"my-1\" />\n            <div className=\"px-1 pb-1\">{footerSlot}</div>\n          </>\n        ) : null}\n      </PopoverContent>\n    </Popover>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/account-switcher.tsx"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/index.ts",
      "content": "export { AccountSwitcher } from \"./account-switcher\";\nexport type {\n  AccountSwitcherProps,\n  AccountSwitcherAriaCurrent,\n  CollapsedPopoverSide,\n  SwitcherItem,\n} from \"./types\";\n",
      "type": "registry:component",
      "target": "components/account-switcher/index.ts"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/types.ts",
      "content": "import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * Single item in the switcher list.\n *\n * Dual-entry items for the same conceptual account (e.g., business-mode +\n * cms-sub-mode of the same business) use distinct keys like `biz-acme` +\n * `cms-biz-acme`. Library enforces key uniqueness at render (L3 / Q2).\n */\nexport interface SwitcherItem {\n  /** Stable unique key. Used for active resolution + React reconciliation. */\n  key: string;\n  /** Trigger + row label. Library does NOT i18n; consumer pre-translates. */\n  label: string;\n  /**\n   * Optional icon. Library accepts both ReactNode (already-rendered JSX) and\n   * ComponentType (lucide-react icons, custom icon components, etc.) so\n   * consumers aren't forced into a single icon library (I-4).\n   */\n  icon?: ReactNode | ComponentType<{ className?: string }>;\n  /**\n   * Optional href. The switcher fires `onSelect(item)` regardless of href —\n   * consumer wires routing inside `onSelect` (router.push for SPA, anchor for\n   * SSR fallbacks, or both). Library does not render `<a>` tags itself.\n   */\n  href?: string;\n}\n\n/**\n * Value applied to the active item's `aria-current` attribute. Default\n * `\"true\"` (generic active-state semantic, correct for a switcher per L14).\n * Consumers using the switcher as primary navigation pick `\"page\"`;\n * stepper-style usages pick `\"step\"`. Pass `false` to omit the attribute.\n */\nexport type AccountSwitcherAriaCurrent =\n  | \"true\"\n  | \"page\"\n  | \"step\"\n  | \"location\"\n  | \"date\"\n  | \"time\"\n  | false;\n\n/**\n * Side of the trigger the popover opens to when `isCollapsed` is true.\n * Defaults to `\"right\"` (collision-aware auto-flip applied by Radix Popover).\n * Override to `\"left\"` for right-edge sidebars, etc. (PQ1).\n */\nexport type CollapsedPopoverSide = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nexport interface AccountSwitcherProps {\n  /** Ordered list. Consumer controls ordering (L2). */\n  items: ReadonlyArray<SwitcherItem>;\n  /**\n   * Currently-active item's key. When null OR not found in items, falls\n   * back to `fallbackActiveItem` (if provided) then to `items[0]`. Empty\n   * items + no fallback → disabled placeholder button (L4, Q1).\n   */\n  activeKey: string | null;\n  /** Fires on item click. Active-item clicks are no-ops at library level (L6). */\n  onSelect: (item: SwitcherItem) => void;\n\n  /** Shown in trigger when `activeKey` doesn't resolve (L4, I-1). */\n  fallbackActiveItem?: SwitcherItem;\n  /** Rendered below items, separated by divider when present (L5, Q7). */\n  footerSlot?: ReactNode;\n  /** When true, trigger collapses to icon-only mode (L10). */\n  isCollapsed?: boolean;\n  /** Side the popover opens to when `isCollapsed` is true. Default `\"right\"` (PQ1). */\n  collapsedPopoverSide?: CollapsedPopoverSide;\n  /** Trigger ARIA label. Default `\"Switch account context\"` (L12). */\n  \"aria-label\"?: string;\n  /** Value applied to active item's `aria-current`. Default `\"true\"` (L14). */\n  ariaCurrent?: AccountSwitcherAriaCurrent;\n\n  /**\n   * Controlled-open state (L13). When provided, makes the popover controlled.\n   * Pair with `onOpenChange` to receive state-change events. Switching between\n   * controlled and uncontrolled (e.g., `open={undefined}` → `open={false}`)\n   * fires a dev-mode warning — pick one mode at mount time.\n   */\n  open?: boolean;\n  /** Initial open state for uncontrolled mode (L13). Default `false`. */\n  defaultOpen?: boolean;\n  /**\n   * Fires when popover open state changes (L13). F-cross-13 typeof-guarded\n   * internally — receives only `boolean`, never `unknown`.\n   */\n  onOpenChange?: (next: boolean) => void;\n\n  /** Pass-through to trigger element (L1 surface contract). */\n  className?: string;\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/types.ts"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/hooks/use-controllable-state.ts",
      "content": "import { useCallback, useEffect, useRef, useState } from \"react\";\n\ninterface UseControllableStateOpts<T> {\n  /** Controlled value. When provided, state is fully controlled by parent. */\n  value?: T;\n  /** Initial value for uncontrolled mode. */\n  defaultValue: T;\n  /** Fires on every state change (both modes). */\n  onChange?: (next: T) => void;\n  /** Component name used in dev warnings. */\n  componentName: string;\n  /** Prop name used in dev warnings (e.g., `\"open\"`). */\n  valuePropName: string;\n}\n\n/**\n * Controlled+uncontrolled state-machine helper.\n *\n * Plan §5.1 + re-validation Finding 1 (⚠️ HIGH):\n *   - Locks the controlled/uncontrolled mode based on the FIRST render's\n *     `value` and dev-warns when consumers flip modes mid-life. Switching\n *     between `value={undefined}` and `value={someBoolean}` is the classic\n *     React anti-pattern; we don't silently swap modes.\n *   - Dev-warns when controlled mode is used without an `onChange` handler\n *     (popover would appear frozen).\n *\n * Both warns are gated on `process.env.NODE_ENV !== \"production\"` so they\n * tree-shake out of prod bundles.\n *\n * Internal helper — not exported from the procomp's public API.\n */\nexport function useControllableState<T>({\n  value,\n  defaultValue,\n  onChange,\n  componentName,\n  valuePropName,\n}: UseControllableStateOpts<T>): readonly [T, (next: T) => void] {\n  const [internal, setInternal] = useState<T>(defaultValue);\n  const isControlled = value !== undefined;\n  const onChangeRef = useRef(onChange);\n  useEffect(() => {\n    onChangeRef.current = onChange;\n  });\n\n  const wasControlledRef = useRef(isControlled);\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") return;\n    if (wasControlledRef.current !== isControlled) {\n      console.warn(\n        `[${componentName}] \\`${valuePropName}\\` switched from ${\n          wasControlledRef.current ? \"controlled\" : \"uncontrolled\"\n        } to ${\n          isControlled ? \"controlled\" : \"uncontrolled\"\n        } mode. Components should not switch modes mid-life; pick one at mount.`,\n      );\n      wasControlledRef.current = isControlled;\n    }\n  }, [isControlled, componentName, valuePropName]);\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") return;\n    if (isControlled && !onChangeRef.current) {\n      const capitalized = valuePropName.charAt(0).toUpperCase() + valuePropName.slice(1);\n      console.warn(\n        `[${componentName}] \\`${valuePropName}\\` is controlled but no onChange handler was provided. ` +\n          `State will appear frozen. Pass \\`on${capitalized}Change\\` ` +\n          `(or use the uncontrolled variant by passing \\`default${capitalized}\\` instead).`,\n      );\n    }\n  }, [isControlled, componentName, valuePropName]);\n\n  const current = isControlled ? (value as T) : internal;\n\n  const set = useCallback(\n    (next: T) => {\n      if (!isControlled) setInternal(next);\n      onChangeRef.current?.(next);\n    },\n    [isControlled],\n  );\n\n  return [current, set] as const;\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/hooks/use-controllable-state.ts"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/lib/enforce-unique-keys.ts",
      "content": "import type { SwitcherItem } from \"../types\";\n\n/**\n * Strip duplicate-key entries and dev-warn about the violation.\n *\n * Per L3 + Q2 — matches React's own key-uniqueness warning semantics:\n *   - Production: silent strip, last-write-wins-by-position (preserves first\n *     occurrence by source order)\n *   - Development: console.warn listing each duplicate key\n *\n * Callers should wrap in `useMemo([items])` so the dedup pass and the\n * (potential) warn fire once per items-reference change, not per render.\n */\nexport function enforceUniqueKeys(\n  items: ReadonlyArray<SwitcherItem>,\n): ReadonlyArray<SwitcherItem> {\n  const seen = new Set<string>();\n  const deduped: SwitcherItem[] = [];\n  const duplicateKeys: string[] = [];\n  for (const item of items) {\n    if (seen.has(item.key)) {\n      duplicateKeys.push(item.key);\n      continue;\n    }\n    seen.add(item.key);\n    deduped.push(item);\n  }\n  if (duplicateKeys.length > 0 && process.env.NODE_ENV !== \"production\") {\n    console.warn(\n      `[account-switcher] Duplicate item keys stripped: ${duplicateKeys.join(\", \")}. ` +\n        `Each item must have a unique \\`key\\`. First occurrence preserved by source order.`,\n    );\n  }\n  return deduped;\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/lib/enforce-unique-keys.ts"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/lib/resolve-active-item.ts",
      "content": "import type { SwitcherItem } from \"../types\";\n\n/**\n * Result of the active-item resolution pipeline (plan §6).\n *\n * Discriminated union so the renderer can react differently per branch —\n * e.g., visually mark `kind: \"fallback\"` if the design later wants to, or\n * disable the trigger in the `kind: \"empty\"` case (Q1 default).\n */\nexport type ResolvedActive =\n  | { kind: \"resolved\"; item: SwitcherItem }\n  | { kind: \"fallback\"; item: SwitcherItem }\n  | { kind: \"first\"; item: SwitcherItem }\n  | { kind: \"empty\" };\n\n/**\n * Resolve the item to show in the trigger.\n *\n * Priority (L4 + Q1):\n *   1. items.find((i) => i.key === activeKey)   → kind: \"resolved\"\n *   2. fallbackActiveItem                       → kind: \"fallback\"\n *   3. items[0]                                 → kind: \"first\"\n *   4. (none)                                   → kind: \"empty\"\n */\nexport function resolveActiveItem(\n  items: ReadonlyArray<SwitcherItem>,\n  activeKey: string | null,\n  fallback: SwitcherItem | undefined,\n): ResolvedActive {\n  if (activeKey !== null) {\n    const match = items.find((i) => i.key === activeKey);\n    if (match) return { kind: \"resolved\", item: match };\n  }\n  if (fallback) return { kind: \"fallback\", item: fallback };\n  if (items.length > 0) return { kind: \"first\", item: items[0]! };\n  return { kind: \"empty\" };\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/lib/resolve-active-item.ts"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/parts/empty-placeholder.tsx",
      "content": "import { cn } from \"@/lib/utils\";\n\ninterface EmptyPlaceholderProps {\n  ariaLabel: string;\n  isCollapsed: boolean;\n  className?: string;\n}\n\n/**\n * Disabled trigger rendered when `items` is empty AND no `fallbackActiveItem`\n * is provided (Q1 default: disabled with placeholder text). Defensive\n * against the source's empty-array crash (I-2).\n */\nexport function EmptyPlaceholder({ ariaLabel, isCollapsed, className }: EmptyPlaceholderProps) {\n  if (isCollapsed) {\n    return (\n      <button\n        type=\"button\"\n        disabled\n        aria-label={ariaLabel}\n        className={cn(\n          \"inline-flex h-10 w-10 cursor-not-allowed items-center justify-center rounded-md border border-dashed border-border bg-card text-muted-foreground opacity-60\",\n          className,\n        )}\n      >\n        <span className=\"text-xs\">—</span>\n      </button>\n    );\n  }\n  return (\n    <button\n      type=\"button\"\n      disabled\n      aria-label={ariaLabel}\n      className={cn(\n        \"inline-flex w-full cursor-not-allowed items-center justify-between rounded-md border border-dashed border-border bg-card px-3 py-2 text-sm text-muted-foreground opacity-60\",\n        className,\n      )}\n    >\n      <span>No items available</span>\n    </button>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/parts/empty-placeholder.tsx"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/parts/render-icon.tsx",
      "content": "import { createElement, isValidElement, type ComponentType, type ReactNode } from \"react\";\n\ntype IconLike = ReactNode | ComponentType<{ className?: string }>;\n\n/**\n * Render an icon prop that accepts either an already-rendered ReactNode\n * (JSX) OR a ComponentType (lucide-react icon, custom component, forwardRef\n * object). Handles all three shapes:\n *\n *   1. JSX element  → return as-is\n *   2. function     → call with { className }\n *   3. forwardRef / memo / exotic component (object with $$typeof) →\n *      use createElement so lucide-react v0.475+ forwardRef icons work\n *      (mirrors the app-sidebar Icon helper fix from 52e5f33).\n *   4. primitive (string/number) → wrap as-is\n *   5. null / undefined → null\n */\nexport function renderIcon(icon: IconLike | undefined, className?: string): ReactNode {\n  if (icon === null || icon === undefined) return null;\n\n  // Already a rendered element\n  if (isValidElement(icon)) return icon;\n\n  // Function component\n  if (typeof icon === \"function\") {\n    return createElement(icon as ComponentType<{ className?: string }>, { className });\n  }\n\n  // forwardRef / memo / exotic (object with $$typeof) — lucide-react v0.475+\n  if (typeof icon === \"object\" && icon !== null && \"$$typeof\" in icon) {\n    return createElement(icon as unknown as ComponentType<{ className?: string }>, { className });\n  }\n\n  // Primitive (string / number) — render directly\n  return icon as ReactNode;\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/parts/render-icon.tsx"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/parts/switcher-item-row.tsx",
      "content": "import type { AriaAttributes, ReactNode } from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport type { AccountSwitcherAriaCurrent, SwitcherItem } from \"../types\";\nimport { renderIcon } from \"./render-icon\";\n\ninterface SwitcherItemRowProps {\n  item: SwitcherItem;\n  isActive: boolean;\n  ariaCurrent: AccountSwitcherAriaCurrent;\n  onSelect: () => void;\n}\n\n/**\n * Single row inside the popover listbox. Renders icon (if present) + label\n * + Check trail (when active). Active rows are no-op on click (L6) —\n * actual handler in the main component already routes around them.\n *\n * `aria-current` resolution per L14 + PQ3 (data-active for CSS hooks\n * regardless of aria value).\n */\nexport function SwitcherItemRow({\n  item,\n  isActive,\n  ariaCurrent,\n  onSelect,\n}: SwitcherItemRowProps) {\n  return (\n    <li role=\"presentation\" className=\"contents\">\n      <button\n        type=\"button\"\n        role=\"option\"\n        aria-selected={isActive || undefined}\n        aria-current={resolveAriaCurrent(isActive, ariaCurrent)}\n        data-active={isActive || undefined}\n        onClick={onSelect}\n        className={cn(\n          \"group flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm transition-colors\",\n          \"hover:bg-accent hover:text-accent-foreground\",\n          \"focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none\",\n          isActive && \"bg-accent/60 text-accent-foreground\",\n        )}\n      >\n        {item.icon ? (\n          <span className=\"flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground group-hover:text-foreground\">\n            {renderIcon(item.icon, \"h-4 w-4\") as ReactNode}\n          </span>\n        ) : null}\n        <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n        {isActive ? (\n          <Check className=\"ml-auto h-4 w-4 shrink-0 text-foreground\" aria-hidden=\"true\" />\n        ) : null}\n      </button>\n    </li>\n  );\n}\n\nfunction resolveAriaCurrent(\n  isActive: boolean,\n  override: AccountSwitcherAriaCurrent,\n): AriaAttributes[\"aria-current\"] | undefined {\n  if (!isActive) return undefined;\n  if (override === false) return undefined;\n  return override;\n}\n",
      "type": "registry:component",
      "target": "components/account-switcher/parts/switcher-item-row.tsx"
    },
    {
      "path": "src/registry/components/navigation/account-switcher/parts/switcher-trigger.tsx",
      "content": "import { forwardRef, type ReactNode } from \"react\";\nimport { ChevronsUpDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport type { SwitcherItem } from \"../types\";\nimport { renderIcon } from \"./render-icon\";\n\ninterface SwitcherTriggerProps {\n  /** Resolved active item (may be a fallback / first / etc.). */\n  activeItem: SwitcherItem | null;\n  /** Trigger ARIA label. Composed with active label per PQ4. */\n  ariaLabel: string;\n  /** ID of the listbox the trigger controls (jsx-a11y combobox requirement). */\n  ariaControls: string;\n  /** When true, render icon-only 40x40 square; no label, no chevron (L10). */\n  isCollapsed: boolean;\n  /** Whether the popover is currently open (drives aria-expanded). */\n  open: boolean;\n  /** Whether the trigger is disabled (e.g., empty-items state). */\n  disabled?: boolean;\n  /** Pass-through className from props. */\n  className?: string;\n  /** Click handler (Popover.Trigger native). */\n  onClick?: () => void;\n}\n\n/**\n * PQ4 — composed aria-label includes the active item label when one resolves\n * (\"Switch account context, current: Acme Corp\").\n */\nexport function composeSwitcherTriggerAriaLabel(\n  ariaLabel: string,\n  activeItem: SwitcherItem | null,\n): string {\n  return activeItem ? `${ariaLabel}, current: ${activeItem.label}` : ariaLabel;\n}\n\n/**\n * Trigger button classes — two branches per `isCollapsed`:\n *   - expanded: full-width with icon + label + chevron, width drives popover\n *     width via --radix-popover-trigger-width\n *   - collapsed: 40x40 icon-only square; popover opens to the side\n *\n * v0.1.1 (F-cross-13 path-b): exported as a class builder because the\n * assembly no longer mounts a <button> via `<PopoverTrigger asChild>` —\n * Base UI's PopoverTrigger has no `asChild` (and the CLI's render-rewrite\n * breaks mixed consumers). The primitive trigger IS the button now:\n * account-switcher.tsx renders\n * `<PopoverTrigger className={switcherTriggerClassName(…)}>` with\n * `<SwitcherTriggerContent>` inside, passing only native DOM props across\n * the shadcn-primitive boundary.\n */\nexport function switcherTriggerClassName({\n  isCollapsed,\n  className,\n}: {\n  isCollapsed: boolean;\n  className?: string;\n}): string {\n  if (isCollapsed) {\n    return cn(\n      \"inline-flex h-10 w-10 items-center justify-center rounded-md border border-border bg-card text-foreground transition-colors\",\n      \"hover:bg-accent hover:text-accent-foreground\",\n      \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n      \"disabled:cursor-not-allowed disabled:opacity-50\",\n      className,\n    );\n  }\n  return cn(\n    \"inline-flex w-full items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground transition-colors\",\n    \"hover:bg-accent hover:text-accent-foreground\",\n    \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n    \"disabled:cursor-not-allowed disabled:opacity-50\",\n    className,\n  );\n}\n\n/**\n * Inner trigger content (icon / initial / label / chevron), backend-neutral —\n * rendered inside whichever button element hosts the trigger.\n */\nexport function SwitcherTriggerContent({\n  activeItem,\n  isCollapsed,\n}: {\n  activeItem: SwitcherItem | null;\n  isCollapsed: boolean;\n}) {\n  if (isCollapsed) {\n    return activeItem?.icon ? (\n      <>{renderIcon(activeItem.icon, \"h-4 w-4\") as ReactNode}</>\n    ) : (\n      <span className=\"text-xs font-medium uppercase\">\n        {activeItem?.label?.charAt(0) ?? \"?\"}\n      </span>\n    );\n  }\n  return (\n    <>\n      {activeItem?.icon ? (\n        <span className=\"flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground\">\n          {renderIcon(activeItem.icon, \"h-4 w-4\") as ReactNode}\n        </span>\n      ) : null}\n      <span className=\"min-w-0 flex-1 truncate text-left\">\n        {activeItem?.label ?? \"—\"}\n      </span>\n      <ChevronsUpDown\n        className=\"ml-auto h-4 w-4 shrink-0 text-muted-foreground\"\n        aria-hidden=\"true\"\n      />\n    </>\n  );\n}\n\n/**\n * Standalone trigger button — v0.1.0 shape, byte-identical rendering, kept\n * for API stability (the part file ships; direct importers keep working).\n * Since v0.1.1 the assembly no longer routes it through\n * `<PopoverTrigger asChild>` (F-cross-13) — see switcherTriggerClassName\n * above for the primitive-trigger path.\n */\nexport const SwitcherTrigger = forwardRef<HTMLButtonElement, SwitcherTriggerProps>(\n  function SwitcherTrigger(\n    { activeItem, ariaLabel, ariaControls, isCollapsed, open, disabled, className, onClick, ...rest },\n    ref,\n  ) {\n    return (\n      <button\n        ref={ref}\n        type=\"button\"\n        role=\"combobox\"\n        aria-haspopup=\"listbox\"\n        aria-expanded={open}\n        aria-controls={ariaControls}\n        aria-label={composeSwitcherTriggerAriaLabel(ariaLabel, activeItem)}\n        disabled={disabled}\n        onClick={onClick}\n        className={switcherTriggerClassName({ isCollapsed, className })}\n        {...rest}\n      >\n        <SwitcherTriggerContent activeItem={activeItem} isCollapsed={isCollapsed} />\n      </button>\n    );\n  },\n);\n",
      "type": "registry:component",
      "target": "components/account-switcher/parts/switcher-trigger.tsx"
    }
  ],
  "categories": [
    "navigation",
    "app-shell"
  ],
  "type": "registry:block"
}