This is the second article of the Building a UI from scratch
series:
- Part #1: Building a UI from scratch, based on a design with ReactJS.
- Part #2: Building a UI from scratch, Responsive Sidebar and Header.
- Part #3: Building a UI from scratch, Responsive Content.
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 thestate
-
isMobile
:true
whenwindow.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();
New articles from this series are coming.
Thanks for reading
Top comments (2)
It's an interesting article, but how can one route child components through sidebar?
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