This is the third 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 Content
As part of the 3rd article, we will see how to make the content that appears in the design (see Part #1).
We want to make it responsive, and we want it to look like this:
As I said in the previous article, we don't have a responsive design to follow, so we will keep it simple, a main breakpoint at 768px.
We can identify 3 main sections in the content:
1- Row of MiniCards
2- Today's trends (graph + stats)
3- Row with 2 cards:
4- Unresolved tickets
5- Tasks
MiniCardComponent.js
This is a simple Component, just a Column with a title
and value
. The content of the column has to be centered.
styles:
container: {
backgroundColor: '#FFFFFF',
border: '1px solid #DFE0EB',
borderRadius: 4,
cursor: 'pointer',
height: 70,
maxWidth: 350,
marginRight: 30,
padding: '24px 32px 24px 32px',
':hover': {
borderColor: '#3751FF',
':nth-child(n) > span': {
color: '#3751FF'
}
}
},
title: {
color: '#9FA2B4',
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'bold',
fontSize: 19,
lineHeight: '24px',
letterSpacing: '0.4px',
marginBottom: 12,
minWidth: 102,
textAlign: 'center'
},
value: {
color: '#252733',
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'bold',
fontSize: 40,
letterSpacing: '1px',
lineHeight: '50px',
textAlign: 'center'
}
Pay attention to the container
styles, on :hover
we want to change the borderColor
and fontColor
of title
and value
, but, by default aphrodite
sets the styles as !important
so, we cannot change the styles of the children (title
, value
) from their parent (container
). To be able to do that we have to import aphrodite
in a differnet way than we normally do.
import { StyleSheet, css } from 'aphrodite/no-important';
Now we can overwrite the styles of the children
from their parents
.
View full file: MiniCardComponent.js
TodayTrendsComponent.js
This component is a Row
with the following structure:
1- Column
3- Row ({ horizontal: space-between })
5- Column with title and subtitle
6- legend
4- Chart
2- Column: list of stats
It can be defined as follows:
where renderLegend
and renderStat
are defined as follows:
for the chart
I have used react-svg-line-chart
, to install it, just type:
yarn add react-svg-line-chart
and these are the styles:
container: {
backgroundColor: '#FFFFFF',
border: '1px solid #DFE0EB',
borderRadius: 4,
cursor: 'pointer'
},
graphContainer: {
marginTop: 24,
marginLeft: 0,
marginRight: 0,
width: '100%'
},
graphSection: {
padding: 24
},
graphSubtitle: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'normal',
fontSize: 12,
lineHeight: '16px',
letterSpacing: '0.1px',
color: '#9FA2B4',
marginTop: 4,
marginRight: 8
},
graphTitle: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'bold',
fontSize: 19,
lineHeight: '24px',
letterSpacing: '0.4px',
color: '#252733'
},
legendTitle: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: '600',
fontSize: 12,
lineHeight: '15px',
letterSpacing: '0.1px',
color: '#9FA2B4',
marginLeft: 8
},
separator: {
backgroundColor: '#DFE0EB',
width: 1,
minWidth: 1,
},
statContainer: {
borderBottom: '1px solid #DFE0EB',
padding: '24px 32px 24px 32px',
height: 'calc(114px - 48px)',
':last-child': {
border: 'none'
}
},
stats: {
borderTop: '1px solid #DFE0EB',
width: '100%'
},
statTitle: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: '600',
fontSize: 16,
lineHeight: '22px',
letterSpacing: '0.3px',
textAlign: 'center',
color: '#9FA2B4',
whiteSpace: 'nowrap',
marginBottom: 6
},
statValue: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'bold',
fontSize: 24,
lineHeight: '30px',
letterSpacing: '0.3px',
textAlign: 'center',
color: '#252733'
}
Notice that container
will become a column
when window.innerWidth <= 1024
, so the stats
column will be stacked under the graph
. At same size the separator
will disappear, and graph
and stats
will fill the whole width.
Pay attention to statContainer
style, where we are setting borders for every child
except for the last.
View full file: TodayTrendsComponent.js
CardComponent.js
As I said before, the 3rd section of the content
page is a Row
with 2 component. These components have many similar characteristics, so we can abstract the design like this:
1- Container (column)
2- Row:
3- Column: title and subtitle (received by props)
4- Link (view details or view all)
5- List of items (received by props)
Code:
renderItem
:
styles:
container: {
backgroundColor: '#FFFFFF',
border: '1px solid #DFE0EB',
borderRadius: 4,
padding: '24px 32px 12px 32px'
},
containerMobile: {
padding: '12px 16px 6px 16px !important'
},
itemContainer: {
marginLeft: -32,
marginRight: -32,
paddingLeft: 32,
paddingRight: 32,
paddingBottom: 18,
paddingTop: 18,
maxHeight: 22,
borderBottom: '1px solid #DFE0EB',
':last-child': {
borderBottom: 'none'
}
},
itemContainerMobile: {
marginLeft: -16,
marginRight: -16,
paddingLeft: 16,
paddingRight: 16
},
link: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: '600',
fontSize: 14,
lineHeight: '20px',
letterSpacing: '0.2px',
color: '#3751FF',
textAlign: 'right',
cursor: 'pointer'
},
subtitle: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'normal',
fontSize: 12,
lineHeight: '16px',
letterSpacing: '0.1px',
color: '#9FA2B4'
},
subtitle2: {
color: '#252733',
marginLeft: 2
},
title: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'bold',
fontSize: 19,
lineHeight: '24px',
letterSpacing: '0.4px',
color: '#252733'
}
See in itemContainer
that all items will have border
except the last one.
View full file: CardComponent.js
UnresolvedTicketsComponent.js
This component will be done based on CardComponent
, it will look like this:
where renderStat
is:
We need styles only for the title
and value
, all the others are set in CardComponent
:
itemTitle: {
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: '600',
fontSize: 14,
lineHeight: '20px',
letterSpacing: '0.2px',
color: '#252733'
},
itemValue: {
color: '#9FA2B4'
}
View full file: UnresolvedTicketsComponent.js
TasksComponent.js
As UnresolvedTicketsComponent
, this component will be done based on CardComponent
, it will look like this:
As you can see, the first item looks different from the others, it has a gray title
and the add button
. The rest of the items are stored in the state:
state = { items: [
{title: 'Finish ticket update', checked: false, tag: TAGS.URGENT },
{title: 'Create new ticket example', checked: false, tag: TAGS.NEW },
{title: 'Update ticket report', checked: true, tag: TAGS.DEFAULT }
]};
These are the possible TAGS:
const TAGS = {
URGENT: { text: 'URGENT', backgroundColor: '#FEC400', color: '#FFFFFF' },
NEW: { text: 'NEW', backgroundColor: '#29CC97', color: '#FFFFFF' },
DEFAULT: { text: 'DEFAULT', backgroundColor: '#F0F1F7', color: '#9FA2B4' },
}
and this is the renderTask
function that will use other 2 functions: renderTag
and renderCheckbox
:
For the checkbox
we are using two new icons that you can copy from here: checkbox-on and checkbox-off,
These are the styles:
addButton: {
backgroundColor: '#F0F1F7',
color: '#9FA2B4',
fontSize: 20,
padding: 7
},
itemTitle: {
color: '#252733',
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: '600',
fontSize: 14,
letterSpacing: '0.2px',
lineHeight: '20px'
},
itemValue: {
color: '#9FA2B4'
},
greyTitle: {
color: '#C5C7CD'
},
tagStyles: {
borderRadius: 5,
cursor: 'pointer',
fontFamily: 'Muli',
fontStyle: 'normal',
fontWeight: 'bold',
fontSize: 11,
letterSpacing: '0.5px',
lineHeight: '14px',
padding: '5px 12px 5px 12px'
},
checkboxWrapper: {
cursor: 'pointer',
marginRight: 16
}
You can see in the repository code that I added some events to do this component interactive.
View full file: TasksComponent.js
ContentComponent.js
Now we have to combine these components into one. As I said before is a Column
with 3 sections:
1- Row of MiniCardComponent
2- TodayTrendsComponent
3- Row with 2 components:
4- UnresolvedTicketsComponent
5- TasksComponent
The first section is probably the most complex, because we have to combine some styles. We have 4 cards
, and we always want the same number of cards in each row regardless of the width of the screen. That is:
- 4 cards in a row, or
- 2 cards in each row, in two different rows, or
- 1 card in each row, in four different rows
but we don't want something like this:
I think it's a good idea if we group them into pairs in this way:
so, when the main row
is wider than the container, it will be divided into two new rows
, and so on.
For TodayTrendsComponent
is easy, we just need to wrap it in a div
to apply some margins.
and the last section is a row
with UnresolvedTicketsComponent
and TasksComponent
that will become a column
when window.innerWidth <= 1024
,
here is the full code:
styles:
cardsContainer: {
marginRight: -30,
marginTop: -30
},
cardRow: {
marginTop: 30,
'@media (max-width: 768px)': {
marginTop: 0
}
},
miniCardContainer: {
flexGrow: 1,
marginRight: 30,
'@media (max-width: 768px)': {
marginTop: 30,
maxWidth: 'none'
}
},
todayTrends: {
marginTop: 30
},
lastRow: {
marginTop: 30
},
unresolvedTickets: {
marginRight: 30,
'@media (max-width: 1024px)': {
marginRight: 0
}
},
tasks: {
marginTop: 0,
'@media (max-width: 1024px)': {
marginTop: 30,
}
}
pay attention to the negative margins of cardsContainer
as they will absorb the excess margins of the elements that are located on the edges, to avoid this kind of things:
View full file: ContentComponent.js
MainComponent (App.js)
To finish we have to include the ContentComponent
in our MainComponent
SidebarComponent.js (important fix)
We have to include a change to our Sidebar
, because at the moment, a transparent layer is filling all the screen on mobile so we cannot click any element.
We are applying these styles to the mainContainerMobile
:
mainContainerMobile: {
...
width: '100%',
minWidth: '100vh',
}
but we want those styles only when the Sidebar
is expanded
, so we will apply these changes to our component:
styles:
...
mainContainerMobile: {
position: 'absolute',
top: 0,
left: 0
},
mainContainerExpanded: {
width: '100%',
minWidth: '100vh',
}
...
View the changes: SidebarComponent.js
View full file: SidebarComponent.js
Conclusion
This is the last article in the series, the goal was to show how to build a UI from scratch based on a design and how to turn it into a responsive UI. We have mainly used aphrodite (^ 2.3.1) for the styles and simple-flexbox (^ 2.2.1) to make the layout.
I hope it was useful.
You can star the repo and follow me on GitHub to see other examples that I will add.
Thanks for reading.
Top comments (0)