Skip to content
Permalink
Browse files
feat(layout): added prop to control toggleable layouts default visibi…
…lity

This adds the new prop `defaultToggleableVisible` and some more
documentation around controlling the visibility of the layout.

Closes #1066
  • Loading branch information
mlaursen committed Feb 13, 2021
1 parent 7fa6b0c commit 6e4a06d
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 12 deletions.
@@ -0,0 +1,8 @@
Since there might be times where it is useful to update the temporary and
toggleable layouts' visibility, this package also exports a `useLayoutConfig`
hook to help out that returns the current configuration and controls.

The example below will give a quick example using this hook to control the
visibility of the navigation panel for non-persistent layouts. This example will
also show how to make toggleable layouts default to being visible with a new
`defaultToggleableVisible` prop introduced in `react-md@2.6.0`.
@@ -0,0 +1,11 @@
.container {
margin: 1rem;
}

.center {
margin: 0 auto;
}

.select {
min-width: 15rem;
}
@@ -0,0 +1,70 @@
import React, { ReactElement, useState } from "react";
import cn from "classnames";
import { Checkbox, Select } from "@react-md/form";
import { Layout, SupportedWideLayout } from "@react-md/layout";
import { Grid } from "@react-md/utils";

import styles from "./ControllingTheLayout.module.scss";
import LayoutVisibility from "./LayoutVisibility";
import CloseButton from "./CloseButton";

const options: SupportedWideLayout[] = [
"temporary",
// 'temporary-mini', // not supported yet
"toggleable",
// 'toggleable-mini', // not supported yet
"clipped",
"floating",
"full-height",
];

export default function ControllingTheLayout(): ReactElement {
const [defaultVisible, setDefaultVisible] = useState(false);
const [desktopLayout, setDesktopLayout] = useState<SupportedWideLayout>(
"full-height"
);

return (
<Layout
id="custom-layout"
title="Toggleable Layout"
navHeaderTitle="Another Title"
tabletLayout="toggleable"
landscapeTabletLayout="toggleable"
desktopLayout={desktopLayout}
largeDesktopLayout={desktopLayout}
defaultToggleableVisible={defaultVisible}
// this is only required since I already have a main element due to the
// documentation site's Layout component
mainProps={{ component: "div" }}
navProps={{
// added a button since there **has** to be something focusable in the
// nav when the temporary layout is chosen
children: <CloseButton />,
}}
>
<Grid columns={1} className={styles.container}>
<Checkbox
id="visibility"
label="Toggleable default visible?"
checked={defaultVisible}
onChange={(event) => setDefaultVisible(event.currentTarget.checked)}
className={styles.center}
/>
<Select
id="desktop-layout"
label="Desktop Layout"
value={desktopLayout}
options={options}
onChange={(nextValue) => {
if (options.includes(nextValue as SupportedWideLayout)) {
setDesktopLayout(nextValue as SupportedWideLayout);
}
}}
className={cn(styles.center, styles.select)}
/>
<LayoutVisibility />
</Grid>
</Layout>
);
}
@@ -0,0 +1,35 @@
import React, { ReactElement } from "react";
import { Button } from "@react-md/button";
import { isPersistentLayout, useLayoutConfig } from "@react-md/layout";

import CodeBlock from "components/Code/CodeBlock";
import Blockquote from "components/Blockquote";

export default function LayoutVisibility(): ReactElement {
const { showNav, hideNav, ...remaining } = useLayoutConfig();
const code = `const config = ${JSON.stringify(
{
showNav: "function",
hideNav: "function",
...remaining,
},
null,
2
)}`;

return (
<div>
<CodeBlock language="typescript">{code}</CodeBlock>
{isPersistentLayout(remaining.layout) && (
<Blockquote>
The visibility cannot be changed for persistent layouts so the buttons
will do nothing.
</Blockquote>
)}
<Button onClick={showNav}>Show</Button>
<Button onClick={hideNav} theme="secondary">
Hide
</Button>
</div>
);
}
@@ -6,18 +6,31 @@ import README from "./README.md";
import ConfigurableLayout from "./ConfigurableLayout";
import configurableLayout from "./ConfigurableLayout.md";

import ControllingTheLayout from "./ControllingTheLayout";
import controllingTheLayout from "./ControllingTheLayout.md";

const modalProps = {
fullPage: true,
fullPageFAB: true,
fullPageProps: {
defaultFocus: "button",
disableAppBar: true,
disableContent: true,
},
};

const demos = [
{
...modalProps,
name: "Configurable Layout",
description: configurableLayout,
children: <ConfigurableLayout />,
fullPage: true,
fullPageFAB: true,
fullPageProps: {
defaultFocus: "button",
disableAppBar: true,
disableContent: true,
},
},
{
...modalProps,
name: "Controlling the Layout",
description: controllingTheLayout,
children: <ControllingTheLayout />,
},
];

@@ -267,6 +267,7 @@ export function Layout({
landscapeTabletLayout = DEFAULT_LANDSCAPE_TABLET_LAYOUT,
desktopLayout = DEFAULT_DESKTOP_LAYOUT,
largeDesktopLayout,
defaultToggleableVisible = false,
customTitle,
title,
titleProps,
@@ -323,6 +324,7 @@ export function Layout({
landscapeTabletLayout={landscapeTabletLayout}
desktopLayout={desktopLayout}
largeDesktopLayout={largeDesktopLayout}
defaultToggleableVisible={defaultToggleableVisible}
>
<SkipToMainContent {...skipProps} mainId={mainId} />
{navAfterAppBar && appBar}
@@ -17,12 +17,13 @@ import {
DEFAULT_TABLET_LAYOUT,
} from "./constants";
import { LayoutConfiguration, SupportedWideLayout } from "./types";
import { getLayoutType, isPersistentLayout } from "./utils";
import { getLayoutType, isPersistentLayout, isToggleableLayout } from "./utils";

/**
* @private
*/
const notInitialized = (name: string) => (): void => {
/* istanbul ignore next */
if (process.env.NODE_ENV !== "production") {
/* eslint-disable no-console */
console.warn(
@@ -91,6 +92,19 @@ export interface LayoutProviderProps extends LayoutConfiguration {
children: ReactNode;
}

/**
* @since 2.6.0
* @private
*/
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
@@ -104,6 +118,7 @@ export function LayoutProvider({
landscapeTabletLayout = DEFAULT_LANDSCAPE_TABLET_LAYOUT,
desktopLayout = DEFAULT_DESKTOP_LAYOUT,
largeDesktopLayout,
defaultToggleableVisible = false,
children,
}: LayoutProviderProps): ReactElement {
const appSize = useAppSize();
@@ -119,12 +134,17 @@ export function LayoutProvider({
const isPersistent = isPersistentLayout(layout);

const { isDesktop } = appSize;
const [visible, setVisible] = useState(isPersistent && isDesktop);
const [visible, setVisible] = useState(
(isPersistent && isDesktop) ||
isToggleableVisible(defaultToggleableVisible, layout)
);
const prevLayout = useRef(layout);
if (prevLayout.current !== layout) {
prevLayout.current = layout;
if (visible !== isPersistent) {
setVisible(isPersistent);
const nextVisible =
isPersistent || isToggleableVisible(defaultToggleableVisible, layout);
if (visible !== nextVisible) {
setVisible(nextVisible);
}
}

0 comments on commit 6e4a06d

Please sign in to comment.