Skip to main content
react-md

useExpandableLayout

function useExpandableLayout(
  options: ExpandableLayoutOptions
): ExpandableLayoutImplementation;

The useExpandableLayout layout hook is used to create a layout where the main navigation can be toggled using a button. The navigation will always appear in a Sheet on mobile, but can be rendered inline on larger viewports.

Example Usage

The useExpandableLayout hook requires the pathname to be provided just like the useTemporaryLayout hook.

import { AppBarTitle } from "@react-md/core/app-bar/AppBarTitle";
import { Button } from "@react-md/core/button/Button";
import { LayoutAppBar } from "@react-md/core/layout/LayoutAppBar";
import { LayoutNav } from "@react-md/core/layout/LayoutNav";
import { Main } from "@react-md/core/layout/Main";
import { useExpandableLayout } from "@react-md/core/layout/useExpandableLayout";
import { Sheet } from "@react-md/core/sheet/Sheet";
import { type ReactElement, type ReactNode } from "react";

import { CustomNavigation } from "./CustomNavigation";

export interface LayoutProps {
  children: ReactNode;
}

export function Layout(props: LayoutProps): ReactElement {
  const { children } = props;

  // choose whichever one for your app
  // nextjs app dir
  const pathname = usePathname();
  // nextjs pages
  const { pathname } = useRouter();
  // react router
  const { pathname } = useHistory();

  const {
    temporary,
    appBarProps,
    expandableNavProps,
    mainProps,
    navToggleProps,
    temporaryNavProps,
    windowSplitterProps,
  } = useExpandableLayout({ pathname });

  return (
    <>
      <LayoutAppBar {...appBarProps}>
        <Button {...navToggleProps} />
        <AppBarTitle>Hello, world!</AppBarTitle>
      </LayoutAppBar>
      <LayoutNav {...expandableNavProps}>
        <CustomNavigation />
      </LayoutNav>
      {temporary && (
        <Sheet {...temporaryNavProps}>
          <CustomNavigation />
        </Sheet>
      )}
      <Main {...mainProps}>{children}</Main>
    </>
  );
}

Conditionally Rendering

If you have a large navigation panel, you can conditionally render the LayoutNav with the persistent boolean returned by the hook which will ensure that the DOM has rehydrated before unmounting to prevent SSR errors.

  const {
    temporary,
+   persistent,
    appBarProps,
    expandableNavProps,
    mainProps,
    navToggleProps,
    temporaryNavProps,
    windowSplitterProps,
  } = useExpandableLayout({ pathname });

  return (
    <>
      <LayoutAppBar {...appBarProps}>
        <Button {...navToggleProps} />
        <AppBarTitle>Hello, world!</AppBarTitle>
      </LayoutAppBar>
-     <LayoutNav {...expandableNavProps}>
-       <CustomNavigation />
-     </LayoutNav>
+     {persistent && (
+       <LayoutNav {...expandableNavProps}>
+         <CustomNavigation />
+       </LayoutNav>
+     )}
      {temporary && (
        <Sheet {...temporaryNavProps}>
          <CustomNavigation />
        </Sheet>
      )}
      <Main {...mainProps}>{children}</Main>
    </>
  );

Parameters

export interface ExpandableLayoutOptions extends TemporaryLayoutOptions {
  /** @defaultValue `"fixed"` */
  appBarPosition?: CssPosition;

  /** @defaultValue `false` */
  defaultExpanded?: UseStateInitializer<boolean>;

  /**
   * Set this to `true` if the expandable navigation should be the full height
   * of the screen. This will also update the app bar so that it is not covered
   * by the navigation. The default behavior is to place the navigation below
   * the fixed header.
   *
   * Set this to `"static"` to make the navigation span the full height of the
   * screen and hide the button until the screen shrinks to the temporary
   * layout type.
   *
   * @defaultValue `false`
   */
  fullHeightNav?: boolean | "static";

  /** @see {@link HorizontalLayoutTransitionOptions} */
  transitionProps?: Omit<HorizontalLayoutTransitionOptions, "transitionIn">;

  /**
   * Set this to `"desktop"` if you want to use the temporary navigation until
   * the viewport is at least desktop width instead of tablet.
   *
   * @defaultValue `"tablet"`
   */
  temporaryUntil?: "tablet" | "desktop";
}

Returns

export interface ExpandableLayoutImplementation
  extends TemporaryLayoutImplementation {
  temporary: boolean;
  persistent: boolean;
  expanded: boolean;
  expandNavigation: () => void;
  collapseNavigation: () => void;
  toggleNavigation: () => void;
  appBarProps: ProvidedLayoutAppBarProps;
  mainProps: ProvidedLayoutMainProps;
  navToggleProps: ProvidedExpandableLayoutNavToggleProps;
  expandableNavProps: ProvidedLayoutNavProps;
}

export type ProvidedLayoutAppBarProps = ProvidedTemporaryLayoutAppBarProps &
  Partial<CSSTransitionElementProps<HTMLElement>>;

export type ProvidedLayoutNavProps = Pick<
  LayoutNavProps,
  "expanded" | "appBarOffset"
>;

export interface ProvidedLayoutMainProps
  extends ProvidedTemporaryLayoutMainProps,
    CSSTransitionElementProps<HTMLElement> {}

export interface ProvidedExpandableLayoutNavToggleProps
  extends ProvidedLayoutNavToggleProps {
  className: string;
}

See Also