DEV Community

Cover image for Building a UI from scratch, Responsive Sidebar and Header
Germán Llorente
Germán Llorente

Posted on • Updated on

Building a UI from scratch, Responsive Sidebar and Header

This is the second article of the Building a UI from scratch series:

Live demo: https://llorentegerman.github.io/react-admin-dashboard/

Repository: https://github.com/llorentegerman/react-admin-dashboard

Responsive design

At the moment, our UI is not responsive, and we want it to look like this:

As we don't have a responsive design to follow, we will keep it simple, only one breakpoint at 768px. So any screen less than 768px will be considered mobile.
The Sidebar will be isolated, on this component will be included: Burger button, Desktop Sidebar and Mobile Sidebar.
SidebarComponent for Desktop is already explained. In this article we will see how to convert it in a responsive sidebar.
In mobile screen (width <= 768px) SidebarComponent could have 2 different states: collapsed (default) or expanded.

Collapsed:

In this state the whole sidebar will be hidden, then the mainBlock (see App.js) will fill the whole width of the screen.
We need a button to expand the Sidebar and we will use a BurgerIcon for that (to copy the burger icon click here). That button will be in a absolute position, over the header:

Expanded

In this state we will show the Sidebar and an outsideLayer that will fill the rest of the screen with a semitransparent background, and if you click it the Sidebar will be closed:

HeaderComponent.js

Since the Burger button will be over the header we need to add some left-margin to the Header Title to avoid this situation:

These are the most important parts of the new styles of HeaderComponent.js, as you can see I have included media queries to apply some special styles for mobile screens:

name: {
    ...,
    '@media (max-width: 768px)': {
        display: 'none' // <--- don't show the name on mobile
    }
},
separator: {
    ...,
    '@media (max-width: 768px)': {
        marginLeft: 12, // <--- less separation on mobile
        marginRight: 12
    }
},
title: {
    ...,
    '@media (max-width: 768px)': {
        marginLeft: 36 <--- to avoid overlapping with Burger button
    },
    '@media (max-width: 468px)': {
        fontSize: 20 <--- new fontSize for small devices. 
    }
}

I have also added a new style for the icons wrappers.

View the changes: HeaderComponent.js

View full file: HeaderComponent.js

SidebarComponent.js

This component contains all the logic and it will change depending on these two variables:

  • expanded: stored in the state
  • isMobile: true when window.innerWidth <= 768

When the Sidebar is expanded, there are two differents ways to collapse it, clicking in some MenuItem or clicking on the outsideLayer. To manage this behaviour there are 2 methods:

onItemClicked = (item) => {
    this.setState({ expanded: false });
    return this.props.onChange(item);
}

toggleMenu = () => this.setState(prevState => ({ expanded: !prevState.expanded }));

toggleMenu will be fired when you click on the Burger button (if sidebar is collapsed) or when you click on the outsideLayer (if sidebar is expanded).

Here is the new version of SidebarComponent:

and here is the renderBurger method:

renderBurger = () => {
    return <div onClick={this.toggleMenu} className={css(styles.burgerIcon)}>
        <IconBurger />
    </div>
}

We are wrapping the component inside a div with position: relative, and that is to allow to the Sidebar fill all the screen, otherwise it will looks like this:

As you can see, we are using the breakpoints property of simple-flexbox, for example:

<Row
    className={css(styles.mainContainer)}
    breakpoints={{ 768: css(styles.mainContainerMobile) }}
>

it means that if window.innerWidth <= 768 mainContainerMobile styles will be applied.

Reading the follow part of the code, you will se that if we are on mobile screen, and expanded = false, just the Burger button will be rendered, and if expanded = true the Sidebar and outsideLayer will be shown.

{(isMobile && !expanded) && this.renderBurger()}
<Column className={css(styles.container)}
    breakpoints={{ 768: css(styles.containerMobile, expanded ? styles.show : styles.hide) }}>
    ...
</Column>
{isMobile && expanded &&
    <div className={css(styles.outsideLayer)} onClick={this.toggleMenu}></div>}

These are the new styles applied to SidebarComponent.js, check that on mobile the position of the container will be absolute to overlay the mainBlock and fill the whole screen. When expanded = false it will be shifted to the left, out of the screen (left: -255px), and when expanded = true it will be shown, shifted to the original position (left: 0px). You can also see the transition property to make a smooth display of the element. outsideLayer will fill the entire screen but will be placed behind the Sidebar (see zIndex):

burgerIcon: {
    cursor: 'pointer',
    position: 'absolute',
    left: 24,
    top: 34
},
container: {
    backgroundColor: '#363740',
    width: 255,
    paddingTop: 32,
    height: 'calc(100% - 32px)'
},
containerMobile: {
    transition: 'left 0.5s, right 0.5s',
    position: 'absolute',
    width: 255,
    height: 'calc(100% - 32px)',
    zIndex: 901
},
mainContainer: {
    height: '100%',
    minHeight: '100vh'
},
mainContainerMobile: {
    position: 'absolute',
    width: '100vw',
    minWidth: '100%',
    top: 0,
    left: 0
},
outsideLayer: {
    position: 'absolute',
    width: '100vw',
    minWidth: '100%',
    height: '100%',
    backgroundColor: 'rgba(0,0,0,.50)',
    zIndex: 900
},
hide: {
    left: -255
},
show: {
    left: 0
}

View the changes: SidebarComponent.js

View full file: SidebarComponent.js

App.js

I have changed the container styles so that it fills all the full height of the screen:

container: {
    height: '100%',
    minHeight: '100vh'
}

and I've included an event to re-render the full application at each resize:

componentDidMount() {
    window.addEventListener('resize', this.resize);
}

componentWillUnmount() {
    window.removeEventListener('resize', this.resize);
}

resize = () => this.forceUpdate();

View the changes: App.js

View full file: App.js

New articles from this series are coming.

Thanks for reading

Top comments (2)

Collapse
 
shivanibali profile image
Shivani Bali

It's an interesting article, but how can one route child components through sidebar?

Collapse
 
llorentegerman profile image
Germán Llorente

Thanks for the comment. I'm not sure if this is what you're looking for, but in this repo:
github.com/llorentegerman/expenses...
I'm using the Sidebar (I rewrote it to use Hooks and add sub items, but the concept is the same), and I'm also using React Router, so, based on the route, you'll see different contents and different item selected in the Sidebar