// Copyright (c) Meta Platforms, Inc. and affiliates. "use client"; /** * @file SideNavCollapseButton.tsx * @input Uses React, StyleX, SideNavCollapseContext, Icon * @output Exports SideNavCollapseButton component * @position Composable toggle button for sidenav collapse * * Place inside SideNav (reads context automatically) or outside * (pass the same controlled `collapsible` config both get). * * SYNC: When modified, update: * - /packages/core/src/SideNav/SideNav.doc.mjs * - /packages/core/src/SideNav/index.ts * - /packages/cli/assets/templates/blocks/components/SideNav/ (showcase blocks) */ import React, { useCallback, type ReactNode } from "react"; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { durationVars, easeVars } from "../theme/tokens.stylex.js"; import { Icon } from "../Icon/index.js"; import { Button } from "../Button/index.js"; import type { BaseProps } from "../BaseProps.js"; import { composeEventHandlers, rtlStyles } from "../utils/index.js"; import { useSideNavCollapse, type SideNavCollapseState, type SideNavControlledCollapsible, type SideNavImperativeCollapseHandle, } from "./SideNavCollapseContext.js"; import { useAppShellMobile } from "../AppShell/AppShellMobileContext.js"; import { useTranslator } from "../i18n/index.js"; import type { ElementSize } from "../SizeContext/SizeContext.js"; // ============================================================================= // Styles // ============================================================================= const styles = { chevronMirror: { k1xSpc: "x78zum5", kGNEyG: "x6s0dn4", kjj79g: "xl56j7k", $$css: true as const, }, chevron: { k1xSpc: "x3nfvp2", kGNEyG: "x6s0dn4", k1ekBW: "x11xpdln", kIyJzY: "xuedmi6 x12w9bfk", kAMwcw: "xlr8y92", $$css: true as const, }, chevronCollapsed: { k3aq6I: "x19jd1h0", $$css: true as const, }, }; // ============================================================================= // Types // ============================================================================= export interface SideNavCollapseButtonProps extends BaseProps { ref?: React.Ref; /** * The same controlled `collapsible` config given to SideNav * (`{isCollapsed, onCollapsedChange}`). Needed only when the button is * rendered outside the sidenav, where collapse context cannot reach it. */ collapsible?: SideNavControlledCollapsible; /** * Imperative handle from SideNav. * * @deprecated Pass `collapsible` instead. */ handleRef?: React.RefObject; /** * Custom button label text. When provided, renders as a text button * with the chevron icon. When omitted, renders as an icon-only button. */ label?: string; /** * Button size. Defaults to the size its container cascades — `sm` in a * SideNav footer — or `md` outside one. Set it for placements with no row * to inherit from, e.g. a button placed in a `TopNav`. */ size?: ElementSize; /** * Custom button content. Overrides the default chevron icon and label. */ children?: ReactNode; } // ============================================================================= // Component // ============================================================================= /** * Composable toggle button for sidenav collapse. * * Place anywhere inside SideNav (header, topContent, footer, footerIcons) * and it reads collapse state from context automatically. For placement * outside the sidenav (e.g. in TopNav or content area), hold the state and * hand the same `collapsible` config to both. * * @example * ``` * }> * ... * * ``` * * @example * ``` * const [isCollapsed, setIsCollapsed] = useState(false); * const collapsible = {isCollapsed, onCollapsedChange: setIsCollapsed}; * } /> * ... * ``` */ export function SideNavCollapseButton({ ref, collapsible, handleRef, label, size, children, onClick: onClickProp, ...props }: SideNavCollapseButtonProps) { const t = useTranslator(); const { isCollapsed, toggle, isCollapsible } = useSideNavCollapseState( collapsible, handleRef, ); const { isMobile } = useAppShellMobile(); // Hide when not collapsible, or when in mobile mode (sidenav is in // the mobile drawer — collapse doesn't apply there) if (!isCollapsible || isMobile) { return null; } return (