# All files / src/layout LayoutProvider.tsx

0% Statements 0/207
0% Branches 0/1
0% Functions 0/1
0% Lines 0/207

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208

     import type { ReactElement, ReactNode } from "react";
    import {
      createContext,
      useCallback,
      useContext,
      useEffect,
      useMemo,
      useState,
    } from "react";
    import { useAppSize } from "../AppSizeProvider";
    import type { CssPosition } from "../types";
    import { useEnsuredId } from "../useEnsuredId";
    import {
      DEFAULT_DESKTOP_LAYOUT,
      DEFAULT_LANDSCAPE_TABLET_LAYOUT,
      DEFAULT_PHONE_LAYOUT,
      DEFAULT_TABLET_LAYOUT,
    } from "./constants";
    import type { LayoutConfiguration, SupportedWideLayout } from "./types";
    import {
      getLayoutType,
      isMiniLayout,
      isPersistentLayout,
      isToggleableLayout,
    } from "./utils";

    /** @internal */
    const noop = (): void => {
      /* istanbul ignore next */
      if (process.env.NODE_ENV !== "production") {
        throw new Error("The `LayoutProvider` has not been mounted.");
      }
    };

    export interface LayoutContext {
      /**
       * The root `id` that was passed to the `Layout` component so that `id`s can
       * be generated for child components.
       */
      baseId: string;

      /**
       * The current layout that is being used based on the app's size.
       */
      layout: SupportedWideLayout;

      /**
       * Boolean if the navigation panel is currently visible. This will always be
       * `true` for persist layout types on desktop.
       */
      visible: boolean;

      /**
       * A function that will set the `visible` state to `true`.
       */
      showNav(): void;

      /**
       * A function that will set the `visible` state to `false`.
       */
      hideNav(): void;

      /**
       * @remarks
       * \@since 2.8.3
       * \@since 6.0.0 Changed to be a type union of position types
       */
      appBarPosition: CssPosition;

      /**
       * Boolean if one of the layout types are mini. This is mostly used internally
       * to prevent the `<main>` element from unmounting (and losing state) for
       * non-fixed app bar layouts.
       *
       * @remarks \@since 2.9.1
       */
      isMiniable: boolean;
    }

    const context = createContext<LayoutContext>({
      baseId: "layout",
      layout: "temporary",
      visible: false,
      showNav: noop,
      hideNav: noop,
      appBarPosition: "fixed",
      isMiniable: false,
    });

    /**
     * Gets the current layout state and configuration.
     */
    export function useLayoutConfig(): LayoutContext {
      return useContext(context);
    }

    const { Provider } = context;

    export interface LayoutProviderProps extends LayoutConfiguration {
      /**
       * The base id for the layout component. This is required since all the child
       * components use this to generate their ids.
       *
       * @remarks \@since 6.0.0 This prop is optional
       * @defaultValue `layout-${useId()}`
       */
      baseId?: string;

      /**
       * The children to render that can inherit the current layout.
       */
      children: ReactNode;

      /**
       * @see {@link LayoutContext.appBarPosition}
       * @defaultValue `true`
       */
      appBarPosition?: CssPosition;
    }

    /**
     * @remarks \@since 2.6.0
     * @internal
     */
    function isToggleableVisible(
      behavior: boolean | "toggleable" | "toggleable-mini",
      layout: SupportedWideLayout
    ): boolean {
      return typeof behavior === "string"
        ? behavior === layout
        : behavior && isToggleableLayout(layout);
    }

    /**
     * Determines the current layout based on the `LayoutConfiguration` and hooks
     * into the `AppSizeListener` to update on resize. This also initializes the
     * `LayLayoutContext` so that a custom layout implementation can be used along
     * with the `useLayoutConfig()` hook and the multiple `Layout` components.
     */
    export function LayoutProvider(props: LayoutProviderProps): ReactElement {
      const {
        baseId: propBaseId,
        phoneLayout = DEFAULT_PHONE_LAYOUT,
        tabletLayout = DEFAULT_TABLET_LAYOUT,
        landscapeTabletLayout = DEFAULT_LANDSCAPE_TABLET_LAYOUT,
        desktopLayout = DEFAULT_DESKTOP_LAYOUT,
        largeDesktopLayout = desktopLayout,
        defaultToggleableVisible = false,
        appBarPosition = "fixed",
        children,
      } = props;
      const appSize = useAppSize();
      const layout = getLayoutType({
        appSize,
        phoneLayout,
        tabletLayout,
        landscapeTabletLayout,
        desktopLayout,
        largeDesktopLayout,
      });
      const isMiniable = [
        phoneLayout,
        tabletLayout,
        landscapeTabletLayout,
        desktopLayout,
        largeDesktopLayout,
      ].some((layout) => !!layout && isMiniLayout(layout));
      const baseId = useEnsuredId(propBaseId, "layout");

      const { isDesktop, isLargeDesktop } = appSize;
      const [visible, setVisible] = useState(
        (isPersistentLayout(layout) && (isDesktop || isLargeDesktop)) ||
          isToggleableVisible(defaultToggleableVisible, layout)
      );

      useEffect(() => {
        setVisible(
          isPersistentLayout(layout) ||
            isToggleableVisible(defaultToggleableVisible, layout)
        );
      }, [defaultToggleableVisible, layout]);

      const showNav = useCallback(() => {
        setVisible(true);
      }, []);

      const hideNav = useCallback(() => {
        if (!isPersistentLayout(layout)) {
          setVisible(false);
        }
      }, [layout]);

      const value = useMemo<LayoutContext>(
        () => ({
          baseId,
          layout,
          visible,
          showNav,
          hideNav,
          appBarPosition,
          isMiniable,
        }),
        [baseId, layout, visible, showNav, hideNav, appBarPosition, isMiniable]
      );

      return <Provider value={value}>{children}</Provider>;
    }