Basic tutorial

Blog Layout

If you haven’t go through Dashboard Layout Tutorial, please check it out first.

Quick start

We will not start from scratch in this tutorial instead reusing layout from Dashboard Layout Tutorial. If you’ve already deleted the code, below is the final source code. Replace it in App.js and you are ready to go!.

Expand to see source code
import React from "react";
import styled from "styled-components";
import Layout, {
  getHeader,
  getDrawerSidebar,
  getSidebarTrigger,
  getSidebarContent,
  getCollapseBtn,
  getContent,
  getFooter,
} from "@mui-treasury/layout";
import Toolbar from "@material-ui/core/Toolbar";
import CssBaseline from "@material-ui/core/CssBaseline";
import {
  NavHeaderMockUp,
  NavContentMockUp,
  ContentMockUp,
  FooterMockUp,
} from '@mui-treasury/mockup/layout';

const Header = getHeader(styled)
const DrawerSidebar = getDrawerSidebar(styled)
const SidebarTrigger = getSidebarTrigger(styled)
const SidebarContent = getSidebarContent(styled)
const CollapseBtn = getCollapseBtn(styled)
const Content = getContent(styled)
const Footer = getFooter(styled)

const scheme = Layout();

scheme.configureHeader((builder) => {
  builder
    .create("appHeader")
    .registerConfig("xs", {
      position: "sticky",
    })
    .registerConfig("md", {
      position: "relative", // won't stick to top when scroll down
    });
});

scheme.configureEdgeSidebar((builder) => {
  builder
    .create("primarySidebar", { anchor: "left" })
    .registerTemporaryConfig("xs", {
      anchor: "left",
      width: "auto", // 'auto' is only valid for temporary variant
    })
    .registerPermanentConfig("md", {
      width: 256, // px, (%, rem, em is compatible)
      collapsible: true,
      collapsedWidth: 64,
    });
});

const App = () => {
  return (
    <Root scheme={scheme}>
      {({ state: { sidebar } }) => (
        <>
          <CssBaseline />
          <Header>
            <Toolbar>
              <SidebarTrigger sidebarId="primarySidebar" />
              <HeaderMockUp />
            </Toolbar>
          </Header>
          <DrawerSidebar sidebarId="primarySidebar">
            <SidebarContent>
              <NavHeaderMockUp collapsed={sidebar.primarySidebar.collapsed} />
              <NavContentMockUp />
            </SidebarContent>
            <CollapseBtn />
          </DrawerSidebar>
          <Content>
            <ContentMockUp />
          </Content>
          <Footer>
            <FooterMockUp />
          </Footer>
        </>
      )}
    </Root>
  );
};

export default App;

Note that I change header_id and sidebar_id to something meaningful. Please use the same id as mine in your code so that everything works as expected

Before we begin, I want to point out something that I haven’t explained in the previous tutorial. Root’s children can also be a function that receive context value (plain object) and you can use that to change, or control your component. Ex, my NavHeaderMockup need props collapsed to reduce the width of avatar when sidebar collapsed. It can receive from primarySidebar in context value.

gif

Here is what context value looks like. (you can console.log in devtool)

{
  state: {
    sidebar: {
      primarySidebar: { // this depends on sidebar id
        open: boolean
        collapsed: boolean
      }
      // other sidebars
    } 
  }
  setOpen: (sidebarId: string, open: boolean) => void
  setCollapsed: (sidebarId: string, collapsed: boolean) => void
  data: object // config that send to components
}

Blog Example

Usually, blog post will have a design like the image below. Header is on top (fixed), Sidebar & Content stay inside a Container Footer can also be inside a Container as well.

image

The first step is to remove PermanentEdgeSidebar and configure inset sidebar by adding this code below.

scheme.configureHeader(...)
scheme.configureEdgeSidebar((builder) => {
  builder
    .create("primarySidebar", { anchor: "left" })
    .registerTemporaryConfig("xs", {
      anchor: "left",
      width: "auto", // 'auto' is only valid for temporary variant
    })
    // comment permanentConfig to not show on screen
    // .registerPermanentConfig("md", {
    //   width: 256, // px, (%, rem, em is compatible)
    //   collapsible: true,
    //   collapsedWidth: 64,
    // });
});

scheme.configureInsetSidebar(builder => {  builder    .create("secondarySidebar", { anchor: 'right' })    .registerStickyConfig('md', {      top: '4rem',      width: 256,    })})
const App = () => {
  ...
}

It means creating inset sidebar with an id: secondarySidebar on the right hand-side with sticky variant and width: 256px at laptop screen and up. Also when user scroll the page, this sidebar will stick from the top 4rem ~ 64px

The config part is done, let’s add InsetSidebar component to the screen. You also need to use InsetContainer so everything works properly. (I encourage you to look at these DOM in devtool once it is rendered to see why we need container)

import Layout, {
  getHeader,
  getDrawerSidebar,
  getSidebarTrigger,
  getSidebarContent,
  getCollapseBtn,
  getContent,
  getFooter,
  getInsetContainer,  getInsetSidebar,} from "@mui-treasury/layout";

// other components
const InsetContainer = getInsetContainer(styled)
const InsetSidebar = getInsetSidebar(styled)

const App = () => {
  return (
    <Root scheme={scheme}>
      {({ state: { sidebar } }) => (
        <>
          <CssBaseline />
          <Header>
            <Toolbar>
              <SidebarTrigger sidebarId="primarySidebar" />
              <HeaderMockUp />
            </Toolbar>
          </Header>
          <DrawerSidebar sidebarId="primarySidebar">
            <SidebarContent>
              <NavHeaderMockUp collapsed={sidebar.primarySidebar.collapsed} />
              <NavContentMockUp />
            </SidebarContent>
            <CollapseBtn />
          </DrawerSidebar>
          <Content>
            <InsetContainer              rightSidebar={                <InsetSidebar sidebarId="secondarySidebar">                  <NavContentMockUp />                </InsetSidebar>              }            >              <ContentMockUp />            </InsetContainer>          </Content>
          <Footer>
            <FooterMockUp />
          </Footer>
        </>
      )}
    </Root>
  );
};

Basically, InsetContainer is a flex container that extends Container from Material-UI. So, you can send whatever props that Material-ui container can receive.

gif

Try changing top value in the config to see what happen to sidebar. (number(px), rem, em, is supported)

Fixed variant

In some blog or documentation (like reactjs.org), the InsetSidebar is fixed to the page.

image

We can achieve this layout by changing builder.registerStickyConfig to builder.registerFixedConfig like this.

scheme.configureInsetSidebar(builder => {
  builder
    .create("secondarySidebar", { anchor: 'right' })
    .registerFixedConfig('md', {      width: 256,    })})

image

So easy right?. By using this config you will get this covered background to the edge automatically without dealing with headache css by yourself. Let’s pause here and give me a clap 👏, thank you.

But nothing is perfect, there is one problem with footer if you scroll down and inspect, you will see this. No worries, we can still make it perfect.

image

Our footer is not aware of FixedInsetSidebar, that’s why it stays at the center of the page. If we want to make it similar to reactjs.org we need to add InsetAvoidingView (got this name from react-native) to make sure that no content is behind the sidebar.

import React from "react";
// ...
import Container from "@material-ui/core/Container";// ...

import Layout, {
  // ...
  getInsetAvoidingView,} from "@mui-treasury/layout";

const InsetAvoidingView = getInsetAvoidingView(styled)

const Blog = () => {
  return (
    <Root scheme={scheme}>
      {({ state: { sidebar } }) => (
        <>
          // ...
          <Footer>            <Container>              <InsetAvoidingView>                <FooterMockUp />              </InsetAvoidingView>            </Container>          </Footer>        </>
      )}
    </Root>
  );
};

Done! we need to wrap with Container first because we want the same alignment as Content and then wrapped by InsetAvoidingView so that its margin prevents the content from FixedInsetSidebar.

Or to make it easy, I have created a component called InsetFooter that does that same thing as I explained above.

import React from "react";
// ...
import Container from "@material-ui/core/Container";// ...

import Layout, {
  // ...
  getInsetFooter,} from "@mui-treasury/layout";

const InsetFooter = getInsetFooter(styled)

const Blog = () => {
  return (
    <Root scheme={scheme}>
      {({ state: { sidebar } }) => (
        <>
          // ...
          <InsetFooter>            <Container>              <InsetAvoidingView>                <FooterMockUp />              </InsetAvoidingView>            </Container>          </Footer>        </>
      )}
    </Root>
  );
};

Congratulations! you have completed this tutorial.

You can look at clone examples to see advance layout like documentation site, e-commerce site or even chat site.

See Full Code
import React from "react";
import styled from "styled-components";
import Layout, {
  getHeader,
  getDrawerSidebar,
  getSidebarTrigger,
  getSidebarContent,
  getCollapseBtn,
  getContent,
  getInsetContainer,
  getInsetSidebar,
  getInsetFooter,
} from "@mui-treasury/layout";
import Toolbar from "@material-ui/core/Toolbar";
import CssBaseline from "@material-ui/core/CssBaseline";
import {
  HeaderMockUp,
  NavHeaderMockUp,
  NavContentMockUp,
  ContentMockUp,
  FooterMockUp,
} from "@mui-treasury/mockup/layout";

const Header = getHeader(styled)
const DrawerSidebar = getDrawerSidebar(styled)
const SidebarTrigger = getSidebarTrigger(styled)
const SidebarContent = getSidebarContent(styled)
const CollapseBtn = getCollapseBtn(styled)
const Content = getContent(styled)
const InsetContainer = getInsetContainer(styled)
const InsetSidebar = getInsetSidebar(styled)
const InsetFooter = getInsetFooter(styled)

const scheme = Layout();

scheme.configureHeader((builder) => {
  builder
    .create("appHeader")
    .registerConfig("xs", {
      position: "sticky",
      initialHeight: 56,
    })
    .registerConfig("md", {
      position: "relative", // won't stick to top when scroll down
      initialHeight: 64,
    });
});

scheme.configureEdgeSidebar((builder) => {
  builder
    .create("primarySidebar", { anchor: "left" })
    .registerTemporaryConfig("xs", {
      width: "auto", // 'auto' is only valid for temporary variant
    });
});

scheme.configureInsetSidebar((builder) => {
  builder
    .create("secondarySidebar", { anchor: "right" })
    .registerFixedConfig("md", {
      width: 256,
    });
});

const Blog = () => {
  return (
    <Root scheme={scheme}>
      {({ state: { sidebar } }) => (
        <>
          <CssBaseline />
          <Header>
            <Toolbar>
              <SidebarTrigger sidebarId="primarySidebar" />
              <HeaderMockUp />
            </Toolbar>
          </Header>
          <DrawerSidebar sidebarId="primarySidebar">
            <SidebarContent>
              <NavHeaderMockUp collapsed={sidebar.primarySidebar.collapsed} />
              <NavContentMockUp />
            </SidebarContent>
            <CollapseBtn />
          </DrawerSidebar>
          <Content>
            <InsetContainer
              rightSidebar={
                <InsetSidebar sidebarId="secondarySidebar">
                  <NavContentMockUp />
                </InsetSidebar>
              }
            >
              <ContentMockUp />
            </InsetContainer>
          </Content>
          <InsetFooter>
            <FooterMockUp />
          </InsetFooter>
        </>
      )}
    </Root>
  );
};

export default Blog;