Skip to content

Commit 84313fc

Browse files
committed
fix(layout): non-fixed AppBar mini layouts
Closes #1101
1 parent 14e6587 commit 84313fc

File tree

8 files changed

+305
-63
lines changed

8 files changed

+305
-63
lines changed

packages/layout/src/LayoutChildren.tsx

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { ReactElement, ReactNode, useEffect, useState } from "react";
1+
import React, {
2+
HTMLAttributes,
3+
ReactElement,
4+
ReactNode,
5+
useEffect,
6+
useState,
7+
} from "react";
28
import { SkipToMainContent, SkipToMainContentProps } from "@react-md/link";
39
import { BaseTreeItem, TreeData } from "@react-md/tree";
410
import { PropsWithRef } from "@react-md/utils";
@@ -8,6 +14,7 @@ import { LayoutAppBar } from "./LayoutAppBar";
814
import { LayoutMain, LayoutMainProps } from "./LayoutMain";
915
import { LayoutNavigation } from "./LayoutNavigation";
1016
import { useLayoutConfig } from "./LayoutProvider";
17+
import { MiniLayoutWrapper } from "./MiniLayoutWrapper";
1118
import { LayoutNavigationItem } from "./types";
1219
import { isMiniLayout } from "./utils";
1320

@@ -60,6 +67,23 @@ export interface LayoutChildrenProps<
6067
*/
6168
miniNavItems?: TreeData<T>;
6269

70+
/**
71+
* This prop allows you to provide additional props to the `<div>` surrounding
72+
* the `LayoutMain` and mini `LayoutNavigation` components.
73+
*
74+
* Note: This additional `<div>` will only be rendered if:
75+
* - the current layout is one of the `mini` types
76+
* - the layout is not using a fixed app bar
77+
* - the `miniNav` prop has not been defined
78+
* - `treeProps` have been provided
79+
*
80+
* @remarks \@since 2.8.3
81+
*/
82+
miniWrapperProps?: PropsWithRef<
83+
HTMLAttributes<HTMLDivElement>,
84+
HTMLDivElement
85+
>;
86+
6387
/**
6488
* The children to display within the layout. This is pretty much required
6589
* since you'll have an empty app otherwise, but it's left as optional just
@@ -88,8 +112,9 @@ export function LayoutChildren({
88112
navToggleProps,
89113
navAfterAppBar = false,
90114
nav: propNav,
91-
miniNav: propMiniNav,
115+
miniNav,
92116
miniNavItems,
117+
miniWrapperProps,
93118
navHeader,
94119
navHeaderProps,
95120
navHeaderTitle,
@@ -151,48 +176,36 @@ export function LayoutChildren({
151176
);
152177
}
153178

154-
let miniNav = propMiniNav;
155-
if (mini && treeProps && typeof miniNav === "undefined") {
156-
let miniTreeProps = treeProps;
157-
if (miniNavItems) {
158-
miniTreeProps = {
159-
...miniTreeProps,
160-
navItems: miniNavItems,
161-
};
162-
}
163-
164-
miniNav = (
165-
<LayoutNavigation
166-
header={navHeader}
167-
headerProps={navHeaderProps}
168-
headerTitle={navHeaderTitle}
169-
headerTitleProps={navHeaderTitleProps}
170-
closeNav={closeNav}
171-
closeNavProps={closeNavProps}
172-
treeProps={miniTreeProps}
173-
{...navProps}
174-
mini
175-
hidden={miniHidden}
176-
/>
177-
);
178-
}
179-
180179
return (
181180
<>
182181
<SkipToMainContent {...skipProps} mainId={mainId} />
183182
{navAfterAppBar && appBar}
184183
{nav}
185184
{!navAfterAppBar && appBar}
186-
{/* mini nav should always be in tab index after app bar */}
187-
{miniNav}
188-
<LayoutMain
189-
headerOffset={fixedAppBar}
190-
{...mainProps}
191-
id={mainId}
185+
<MiniLayoutWrapper
192186
mini={mini}
187+
miniNav={miniNav}
188+
miniHidden={miniHidden}
189+
containerProps={miniWrapperProps}
190+
miniNavItems={miniNavItems}
191+
treeProps={treeProps}
192+
header={navHeader}
193+
headerProps={navHeaderProps}
194+
headerTitle={navHeaderTitle}
195+
headerTitleProps={navHeaderTitleProps}
196+
closeNav={closeNav}
197+
closeNavProps={closeNavProps}
193198
>
194-
{children}
195-
</LayoutMain>
199+
<LayoutMain
200+
headerOffset={fixedAppBar}
201+
mini={mini}
202+
miniHidden={miniHidden}
203+
{...mainProps}
204+
id={mainId}
205+
>
206+
{children}
207+
</LayoutMain>
208+
</MiniLayoutWrapper>
196209
</>
197210
);
198211
}

packages/layout/src/LayoutMain.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { bem, useIsUserInteractionMode } from "@react-md/utils";
77

88
import { DEFAULT_LAYOUT_MAIN_CLASSNAMES } from "./constants";
99
import { useLayoutConfig } from "./LayoutProvider";
10-
import { isTemporaryLayout } from "./utils";
10+
import { isTemporaryLayout, isToggleableLayout } from "./utils";
1111

1212
export interface LayoutMainProps extends HTMLAttributes<HTMLDivElement> {
1313
/**
@@ -38,6 +38,16 @@ export interface LayoutMainProps extends HTMLAttributes<HTMLDivElement> {
3838
*/
3939
mini?: boolean;
4040

41+
/**
42+
* Boolean if the mini layout is currently hidden to help determine if
43+
* specific mini styles should be applied when the {@link LayoutContext.fixedAppBar}
44+
* config is `false`.
45+
*
46+
* @internal
47+
* @remarks \@since 2.8.3
48+
*/
49+
miniHidden?: boolean;
50+
4151
/**
4252
* The transition timeout to use for the toggleable `LayoutNavigation` either
4353
* comes into view or expands from mini to full-width. The transition can be
@@ -69,6 +79,7 @@ export const LayoutMain = forwardRef<HTMLDivElement, LayoutMainProps>(
6979
timeout: propTimeout = DEFAULT_SHEET_TIMEOUT,
7080
classNames = DEFAULT_LAYOUT_MAIN_CLASSNAMES,
7181
mini = false,
82+
miniHidden = false,
7283
...props
7384
},
7485
forwardedRef
@@ -88,7 +99,7 @@ export const LayoutMain = forwardRef<HTMLDivElement, LayoutMainProps>(
8899
tabIndex = -1;
89100
}
90101

91-
const { layout, visible } = useLayoutConfig();
102+
const { layout, visible, fixedAppBar } = useLayoutConfig();
92103
let navOffset = propNavOffset;
93104
if (typeof navOffset === "undefined") {
94105
navOffset = visible && !isTemporaryLayout(layout);
@@ -120,15 +131,24 @@ export const LayoutMain = forwardRef<HTMLDivElement, LayoutMainProps>(
120131
},
121132
});
122133

134+
const isMini = mini && (fixedAppBar || miniHidden);
135+
const isMiniOffset =
136+
mini &&
137+
navOffset &&
138+
!fixedAppBar &&
139+
visible &&
140+
isToggleableLayout(layout);
141+
123142
return (
124143
<Component
125144
{...props}
126145
ref={ref}
127146
tabIndex={tabIndex}
128147
className={cn(
129148
styles({
130-
mini: mini && (isTemporaryLayout(layout) || !visible),
131-
"nav-offset": mini,
149+
mini: isMini && (isTemporaryLayout(layout) || !visible),
150+
"nav-offset": isMini,
151+
"mini-offset": isMiniOffset,
132152
"header-offset": headerOffset,
133153
}),
134154
className

packages/layout/src/LayoutNavigation.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { forwardRef, ReactNode } from "react";
22
import cn from "classnames";
33
import { Sheet, SheetProps } from "@react-md/sheet";
4-
import { BaseTreeItem } from "@react-md/tree";
4+
import { BaseTreeItem, TreeItemRenderer } from "@react-md/tree";
55
import { bem, PropsWithRef } from "@react-md/utils";
66

77
import { LayoutCloseNavigationButtonProps } from "./LayoutCloseNavigationButton";
@@ -17,6 +17,7 @@ import {
1717
isTemporaryLayout,
1818
isToggleableLayout,
1919
} from "./utils";
20+
import { defaultMiniNavigationItemRenderer } from "./defaultMiniNavigationItemRenderer";
2021

2122
export type LayoutNavigationSheetProps = Omit<
2223
SheetProps,
@@ -94,6 +95,18 @@ export interface LayoutNavigationProps<
9495
* @remarks \@since 2.7.0
9596
*/
9697
mini?: boolean;
98+
99+
/**
100+
* Boolean if the mini navigation should be treated as a "sticky" element.
101+
* This should really only be `true` if disabling the fixed `AppBar` behavior
102+
* in the `Layout`.
103+
*
104+
* @remarks \@since 2.8.3
105+
*/
106+
sticky?: boolean;
107+
108+
/** @remarks \@since 2.8.3 */
109+
miniNavItemRenderer?: TreeItemRenderer<T>;
97110
}
98111

99112
const styles = bem("rmd-layout-navigation");
@@ -120,6 +133,8 @@ export const LayoutNavigation = forwardRef<
120133
closeNav,
121134
closeNavProps,
122135
treeProps,
136+
sticky = false,
137+
miniNavItemRenderer = defaultMiniNavigationItemRenderer,
123138
...props
124139
},
125140
ref
@@ -172,14 +187,22 @@ export const LayoutNavigation = forwardRef<
172187
className={cn(
173188
styles({
174189
mini,
190+
sticky,
175191
floating,
176192
"header-offset": layout === "clipped" || floating,
177193
}),
178194
className
179195
)}
180196
>
181197
{header}
182-
{treeProps && <LayoutTree {...treeProps} mini={mini} />}
198+
{treeProps && (
199+
<LayoutTree
200+
miniItemRenderer={miniNavItemRenderer}
201+
sticky={mini && sticky}
202+
{...treeProps}
203+
mini={mini}
204+
/>
205+
)}
183206
{children}
184207
</Sheet>
185208
);

packages/layout/src/LayoutTree.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import React, { CSSProperties, forwardRef, useEffect, useRef } from "react";
22
import cn from "classnames";
3-
import { BaseTreeItem, Tree, TreeData, TreeProps } from "@react-md/tree";
3+
import {
4+
BaseTreeItem,
5+
Tree,
6+
TreeData,
7+
TreeItemRenderer,
8+
TreeProps,
9+
} from "@react-md/tree";
410
import { bem } from "@react-md/utils";
511

612
import { defaultMiniNavigationItemRenderer } from "./defaultMiniNavigationItemRenderer";
@@ -11,9 +17,8 @@ import { isTemporaryLayout } from "./utils";
1117

1218
const styles = bem("rmd-layout-nav");
1319

14-
export type BaseLayoutTreeProps<
15-
T extends BaseTreeItem = LayoutNavigationItem
16-
> = Omit<TreeProps<T>, "id" | "data" | "aria-label" | "aria-labelledby">;
20+
export type BaseLayoutTreeProps<T extends BaseTreeItem = LayoutNavigationItem> =
21+
Omit<TreeProps<T>, "id" | "data" | "aria-label" | "aria-labelledby">;
1722

1823
export interface LayoutTreeProps<T extends BaseTreeItem = LayoutNavigationItem>
1924
extends BaseLayoutTreeProps<T> {
@@ -47,6 +52,22 @@ export interface LayoutTreeProps<T extends BaseTreeItem = LayoutNavigationItem>
4752
*/
4853
mini?: boolean;
4954

55+
/**
56+
* Boolean if the mini navigation should be treated as a "sticky" element.
57+
* This should really only be `true` if disabling the fixed `AppBar` behavior
58+
* in the `Layout`.
59+
*
60+
* @remarks \@since 2.8.3
61+
*/
62+
sticky?: boolean;
63+
64+
/**
65+
* The {@link TreeItemRenderer} to use if the `mini` prop is enabled.
66+
*
67+
* @remarks \@since 2.8.3
68+
*/
69+
miniItemRenderer?: TreeItemRenderer<T>;
70+
5071
/**
5172
* Optional style to provide to the `<nav>` element surrounding the tree
5273
*/
@@ -84,14 +105,14 @@ export const LayoutTree = forwardRef<HTMLUListElement, LayoutTreeProps>(
84105
"aria-label": ariaLabel = ariaLabelledBy ? undefined : "Navigation",
85106
className,
86107
mini = false,
108+
sticky = false,
87109
navStyle,
88110
navClassName,
89111
navItems,
90112
labelKey = "children",
91113
valueKey = "children",
92-
itemRenderer = mini
93-
? defaultMiniNavigationItemRenderer
94-
: defaultNavigationItemRenderer,
114+
itemRenderer = defaultNavigationItemRenderer,
115+
miniItemRenderer = defaultMiniNavigationItemRenderer,
95116
selectedIds,
96117
disableTemporaryAutoclose = false,
97118
...props
@@ -127,7 +148,7 @@ export const LayoutTree = forwardRef<HTMLUListElement, LayoutTreeProps>(
127148
<nav
128149
id={`${id}-nav`}
129150
style={navStyle}
130-
className={cn(styles({ mini }), navClassName)}
151+
className={cn(styles({ sticky, grow: !sticky }), navClassName)}
131152
>
132153
<Tree
133154
{...props}
@@ -139,7 +160,7 @@ export const LayoutTree = forwardRef<HTMLUListElement, LayoutTreeProps>(
139160
labelKey={labelKey}
140161
valueKey={valueKey}
141162
selectedIds={selectedIds}
142-
itemRenderer={itemRenderer}
163+
itemRenderer={mini ? miniItemRenderer : itemRenderer}
143164
className={cn("rmd-layout-tree", className)}
144165
/>
145166
</nav>

0 commit comments

Comments
 (0)