# All files / src/layout LayoutMain.tsx

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

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

     import { cnb } from "cnbuilder";
    import type { HTMLAttributes } from "react";
    import { forwardRef, useRef } from "react";
    import { useUserInteractionMode } from "../interaction";
    import { DEFAULT_SHEET_TIMEOUT } from "../sheet";
    import type { CSSTransitionClassNames, TransitionTimeout } from "../transition";
    import { useCSSTransition } from "../transition";
    import { bem } from "../utils";

    import { DEFAULT_LAYOUT_MAIN_CLASSNAMES } from "./constants";
    import { useLayoutConfig } from "./LayoutProvider";
    import { isTemporaryLayout, isToggleableLayout } from "./utils";

    export interface LayoutMainProps extends HTMLAttributes<HTMLDivElement> {
      /**
       * The component to render the main element as. This should normally stay as
       * the default of `"main"`, but if you want to have multiple `Layout` on the
       * page for some reason, you'll need to use `"div"` for the other `Layout`s
       * since you can only have one `<main>` per page (unless you set the `hidden`
       * attribute on all the others).
       *
       * @defaultValue `"main"`
       */
      component?: "div" | "main";

      /**
       * Boolean if the main element should be offset by the current navigation
       * tree's width.
       *
       * @defaultValue `false`
       */
      navOffset?: boolean;

      /**
       * Boolean if the main element should be offset by the `AppBar`'s height
       *
       * @defaultValue `false`
       */
      headerOffset?: boolean;

      /**
       * Boolean of there is a mini nav visible within the layout. This makes sure
       * that the content if offset by the current nav's width when needed.
       *
       * @remarks \@since 2.7.0
       * @defaultValue `false`
       */
      mini?: boolean;

      /**
       * Boolean if the mini layout is currently hidden to help determine if
       * specific mini styles should be applied when the {@link LayoutContext.appBarPosition}
       * config is `""`.
       *
       * @internal
       * @remarks \@since 2.8.3
       * @defaultValue `false`
       */
      miniHidden?: boolean;

      /**
       * The transition timeout to use for the toggleable `LayoutNavigation` either
       * comes into view or expands from mini to full-width. The transition can be
       * disabled by setting this value to `0`.
       *
       * @defaultValue `DEFAULT_SHEET_TIMEOUT`
       * @see {@link DEFAULT_SHEET_TIMEOUT}
       */
      timeout?: TransitionTimeout;

      /**
       * The transition classnames to use for the toggleable `LayoutNavigation`
       * either comes into view or expands from mini to full-width.
       *
       * @defaultValue `DEFAULT_LAYOUT_MAIN_CLASSNAMES`
       * @see {@link DEFAULT_LAYOUT_MAIN_CLASSNAMES}
       */
      classNames?: CSSTransitionClassNames;
    }

    const styles = bem("rmd-layout-main");

    /**
     * This is the `<main>` element for your app that has some built in styles to be
     * able to update based on the current layout types.
     */
    export const LayoutMain = forwardRef<HTMLDivElement, LayoutMainProps>(
      function LayoutMain(props, nodeRef) {
        const {
          className,
          tabIndex: propTabIndex,
          component: Component = "main",
          navOffset: propNavOffset,
          headerOffset = false,
          timeout: propTimeout = DEFAULT_SHEET_TIMEOUT,
          classNames = DEFAULT_LAYOUT_MAIN_CLASSNAMES,
          mini = false,
          miniHidden = false,
          ...remaining
        } = props;
        const mode = useUserInteractionMode();

        // this makes it so that the SkipToMainContent button can still
        // focus the `<main>` element, but the `<main>` will no longer be
        // focused if the user clicks inside. This is super nice since one
        // of my bigger patterns is to click somewhere then press tab to
        // focus a specific element. Without this fix, the first element in
        // the `<main>` tag would be focused instead of the closest focusable
        // element to the click area.
        let tabIndex = propTabIndex;
        if (mode === "keyboard" && typeof propTabIndex === "undefined") {
          tabIndex = -1;
        }

        const { layout, visible, appBarPosition } = useLayoutConfig();
        let navOffset = propNavOffset;
        if (typeof navOffset === "undefined") {
          navOffset = visible && !isTemporaryLayout(layout);
        }

        let timeout = propTimeout;
        const prevLayout = useRef(layout);
        if (prevLayout.current !== layout) {
          // this is kind of weird and hacky, but this will allow for the required
          // classnames to be applied to the main element based on the current
          // layout type without needing a unique `key` for the main content. this
          // is super nice since we really don't want to remount the full app each
          // time the layout changes.
          timeout = 0;
        }

        const isMini = mini && (!!appBarPosition || miniHidden);
        const isMiniOffset =
          mini &&
          navOffset &&
          !appBarPosition &&
          visible &&
          isToggleableLayout(layout);

        const { elementProps } = useCSSTransition<HTMLDivElement>({
          nodeRef,
          transitionIn: !!navOffset,
          temporary: false,
          className: cnb(
            styles({
              mini: isMini && (isTemporaryLayout(layout) || !visible),
              "nav-offset": isMini,
              "mini-offset": isMiniOffset,
              "header-offset": headerOffset,
            }),
            className
          ),
          timeout,
          classNames,
          onEntered: () => {
            prevLayout.current = layout;
          },
          onExited: () => {
            prevLayout.current = layout;
          },
        });

        return <Component {...remaining} {...elementProps} tabIndex={tabIndex} />;
      }
    );