React Static Website with Material UI theme

In this tutorial you'll learn about integration of Material UI with React JS and how to fetch static JSON data to display.
Demo

First, head over to nodejs.org and install Node if you already don't have it on your machine. Now, open up the Terminal on Mac or Command Prompt on Windows and run the following command to install create-react-app package.

npm install -g create-react-app

Create Your First React App

Now, type the following command to create a new react app:

create-react-app example1

Now, go to the project folder:

cd example1

Install Dependencies for this Project

npm install @material-ui/core
npm install @material-ui/icons
npm install --save moment react-moment
npm install --save bootstrap-4-react
npm install react-social-icons --save-dev
npm install --save react-swipeable-views react-motion

The "package.json" will get update and may look like as below (**Don't update manually**)

package.json

Example

{
  "name": "example1",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^3.9.2",
    "@material-ui/icons": "^3.0.2",
    "@types/react-swipeable-views": "^0.13.0",
    "bootstrap-4-react": "0.0.58",
    "moment": "^2.24.0",
    "react": "^16.8.5",
    "react-bootstrap": "^1.0.0-beta.6",
    "react-dom": "^16.8.5",
    "react-moment": "^0.8.4",
    "react-motion": "^0.5.2",
    "react-router-dom": "^5.0.0",
    "react-scripts": "2.1.8",
    "react-swipeable-views": "^0.13.1",
    "reactstrap": "^7.1.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ],
  "devDependencies": {
    "react-social-icons": "^4.1.0"
  }
}

Now create a new file as specified below with sample data in JSON format.

src\data\profile.json

Example

{
    "Name": "Rocky Smith",
    "Position": "Web Designer",
    "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua labo ris nisi ut aliquip ex ea commodo consequat.",
    "Address": "New Delhi, India",
    "Company": "Fizer Technologies",
    "School": "University of Demo",
    "SocialMedias": [
      "https://github.com/",
      "https://www.linkedin.com/",
      "https://www.facebook.com/"
    ],
    "Experiences": [
      {
        "companyName": "Demo1 Technologies",
        "logo": "https://upload.wikimedia.org/wikipedia/commons/a/ac/No_image_available.svg",
        "url": "https://www.google.com/",
        "roles": [
          {
            "title": "Full Stack Developer",
            "description": "Built and updated various Chrome Extensions.",
            "startDate": "2017-01-01",
            "endDate": "2017-05-01",
            "location": "New York, USA"
          }
        ]
      },
      {
        "companyName": "Demo2 Technologies",
        "logo": "https://upload.wikimedia.org/wikipedia/commons/a/ac/No_image_available.svg",
        "url": "https://www.google.com/",
        "roles": [
          {
            "title": "UI Designer",
            "description": "Design user-fridendly web page",
            "startDate": "2016-05-01",
            "endDate": "2016-09-01",
            "location": "Beijing, China"
          }
        ]
      }
    ],
    "Skills": [
      {
        "Area": "Programming Language",
        "SkillSet": [
          {
            "Name": "Java",
            "Hot": true
          },
          {
            "Name": "C#",
            "Hot": false
          },
          {
            "Name": "Python",
            "Hot": false
          }
        ]
      },
      {
        "Area": "Web-Based Application Development",
        "SkillSet": [
          {
            "Name": "JavaScript (ES5, ES6)",
            "Hot": true
          },
          {
            "Name": "TypeScript",
            "Hot": false
          },
          {
            "Name": "HTML5",
            "Hot": true
          },
          {
            "Name": "CSS (SCSS/SASS)",
            "Hot": true
          },
          {
            "Name": "React",
            "Hot": true
          }
        ]
      }      
    ]   
  }

You need to replace the "src/App.js" content using the below code to get a basic template with left and body panel.

src\App.js

Example

import React, { Component } from 'react';
import LeftPanel from './components/left-panel/LeftPanel';
import BodyPanel from './components/body-panel/BodyPanel';
 
class App extends Component {
    render() {
        return (
            <div className="App">
                <LeftPanel></LeftPanel>
                <BodyPanel></BodyPanel>
            </div>
        );
    }
}
 
export default App;

src\components\left-panel\LeftPanel.js

Example

import React, { Component } from 'react';
import Drawer from '@material-ui/core/Drawer';
import UserDetail from '../user-detail/UserDetail'
import './LeftPanel.css';
 
const styles = {
    drawer: 'drawer',
    drawerPaper: 'drawerPaper'
}
 
class LeftPanel extends Component {
    render() {
        return (
            <div>
                <Drawer className={styles.drawer} variant="permanent" classes={{paper: styles.drawerPaper}} anchor="left">
                    <UserDetail></UserDetail>
                </Drawer>
            </div>
 
        );
    }
}
 
export default LeftPanel;

src\components\left-panel\LeftPanel.css

Example

.drawer {
  width: 500px;
}
 
.drawer .drawerPaper {
  width: 500px;
  background-color: #b0914f;
  box-shadow: 0.5px 0px 11px 0px rgba(0,0,0, 1), 0px 3px 4px 0px rgba(0,0,0,0.14), 0px 3px 3px -2px rgba(0,0,0,0.12);
}

src\components\user-detail\UserDetail.js

Example

import React, { Component } from 'react';
import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import Grid from '@material-ui/core/Grid';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Avatar from '@material-ui/core/Avatar';
import LocationCityIcon from '@material-ui/icons/LocationCity';
import WorkIcon from '@material-ui/icons/Work';
import { SocialIcon } from 'react-social-icons';
import SchoolIcon from '@material-ui/icons/School';
import profile from '../../data/profile';
import './UserDetail.css';
 
const styles = {
    infoPaper: 'infoPaper',
    infoGrid: 'infoGrid',
    bigAvatar: 'bigAvatar',
    infoList: 'infoList',
    infoIcon: 'infoIcon',
    divider: 'divider',
    socialMedias: 'socialMedias',
    socialMediasIcon: 'socialMediasIcon'
}
 
const socialMediaList = profile.SocialMedias;
 
class UserDetail extends Component {
    render() {
        return (
            <div>
                <Grid container className={styles.infoGrid} justify="center" alignItems="center">
                    <Grid item>
                        <Avatar alt={profile.Name} className={styles.bigAvatar} src={"https://upload.wikimedia.org/wikipedia/commons/a/ac/No_image_available.svg"} />
                    </Grid>
                    <Grid item xs={12}>
                        <Typography variant="h3" className="name">
                            {profile.Name}
                        </Typography>
                        <Typography variant="h5" className="position">
                            {profile.Position}
                        </Typography>
                        <Typography variant="h6" className="introduction">
                        {profile.Description}
                        </Typography>
                    </Grid>
                    <Divider className={styles.divider} variant="middle" />
                    <Grid item xs={12}>
                        <List className={styles.infoList}>
                            <ListItem>
                                <Avatar className={styles.infoIcon}>
                                    <LocationCityIcon />
                                </Avatar>
                                <ListItemText primary={profile.Address}/>
                            </ListItem>
                            <ListItem>
                                <Avatar className={styles.infoIcon}>
                                    <WorkIcon />
                                </Avatar>
                                <ListItemText primary={profile.Company}/>
                            </ListItem>
                            <ListItem>
                                <Avatar className={styles.infoIcon}>
                                    <SchoolIcon />
                                </Avatar>
                                <ListItemText primary={profile.School}/>
                            </ListItem>
                        </List>
                    </Grid>
                    <Divider className={styles.divider} variant="middle" />
                    <Grid item xs={12} className={styles.socialMedias}>
                    {socialMediaList.map(s => (<SocialIcon className={styles.socialMediasIcon} key={s} url={s} bgColor="#fff"/>))}
                    </Grid>
                </Grid>
            </div>
        );
    }
}
 
export default UserDetail;

src\components\user-detail\UserDetail.css

Example

.infoGrid .bigAvatar {
    margin: 10px;
    width: 200px;
    height: 200px;
    border: 3px solid rgba(232, 232, 232, 0.8);
    box-shadow: 1px 1px 4px 0px rgba(0, 0, 0, 0.75) inset;
    -moz-box-shadow: 1px 1px 4px 0px rgba(0, 0, 0, 0.75) inset;
    -webkit-box-shadow: 1px 1px 4px 0px rgba(0, 0, 0, 0.75) inset;
}
 
.infoGrid .name {
    text-align: center;
    color: white
}
 
.infoGrid .position {
    text-align: center;
    margin-top: 5px;
    color: white;
    font-style: italic;
}
 
.infoGrid .introduction {
    color: white;
    margin: 15px 70px;
    text-align: center;
}
 
.infoGrid .divider {
    margin: 0;
    border: none;
    flex-shrink: 0;
    background-color: white;
    width: 320px;
}
 
.infoGrid .infoList {
    margin: 10px 114px;
    color: white;
}
 
.infoGrid .socialMedias {
    text-align: center;
    margin-top: 25px;
}
 
.infoGrid .socialMediasIcon {
    margin: 0 20px;
}
 
.infoList span {
    color: white;
}
 
.infoList .infoIcon {
    color: #b0914f;
    background-color: white;
}

src\components\body-panel\BodyPanel.js

Example

import React, { Component } from 'react';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import { withStyles } from '@material-ui/core/styles';
import ExperienceDetail from '../experience-detail/ExperienceDetail';
import SkillDetail from '../skill-detail/SkillDetail';
import SwipeableViews from 'react-swipeable-views';
import './BodyPanel.css';
 
const styles = {
    tabPanelRoot: 'tabPanelRoot',
    tabPanelText: 'tabPanelText',
    tabContainer: 'tabContainer'
}
 
class BodyPanel extends Component {
 
    constructor(props) {
        super(props);
        this.handleChangeIndex = this.handleChangeIndex.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.state = {
            value: 0,
        };
    }
 
    handleChange = (event, value) => {
        this.setState({ value });
    };
 
    handleChangeIndex = index => {
        this.setState({ value: index });
      };
 
    render() {
        const { value } = this.state;
        const { theme } = this.props;
        return (
            <div className={styles.tabPanelRoot}>
                <AppBar position="static">
                    <Tabs value={value} variant="fullWidth" onChange={this.handleChange}>
                        <Tab className={styles.tabPanelText} label="My Experience" />
                        <Tab className={styles.tabPanelText} label="My Skills" />
                    </Tabs>
                </AppBar>
                <SwipeableViews animateHeight={true} className={styles.tabContainer} axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} index={this.state.value} onChangeIndex={this.handleChangeIndex}>
                    <ExperienceDetail dir={theme.direction}></ExperienceDetail>
                    <SkillDetail dir={theme.direction}></SkillDetail>                    
                </SwipeableViews>
            </div>
        );
    }
}
 
export default withStyles(styles, { withTheme: true })(BodyPanel);

src\components\body-panel\BodyPanel.css

Example

.tabPanelRoot {
    margin-left: 500px;
}
 
.tabPanelRoot header {
    background-color: #b0914f;
}
.tabPanelText {
    outline: none !important;
}
 
.tabPanelText span {
    font-weight: bold;
}

src\components\experience-detail\ExperienceDetail.js

Example

import React, { Component } from 'react';
import { Container, Row, Col } from "reactstrap";
import profile from "../../data/profile";
import moment from "moment";
import { Media } from "reactstrap";
 
import './ExperienceDetail.css';
 
const styles = {
    experienceContainer: 'experienceContainer'
}
 
class ExperienceDetail extends Component {
    render() {
        return (
            <Container className={styles.experienceContainer}>
                <Row>
                    <Col>
                        {profile.Experiences.map((experience, i) => {
                            moment.locale('en');
                            experience.roles.reduce(function (cnt, role) {
                                const startDate = moment(role.startDate);
                                const timeEnd = moment(role.currentJob ? new Date() : new Date(role.endDate));
                                const duration = moment.duration(timeEnd.diff(startDate));
                                return Number(cnt) + Number(duration.asMonths().toPrecision(1));
                            }, 0);
 
                            return (
                                <div key={i}>
                                    <Media>
                                        <Media left top href={experience.url}>
                                            <Media object src={experience.logo} alt={experience.companyName} />
                                        </Media>
                                        <Media body>
                                            <Media heading>
                                                <a href={experience.url}>{experience.companyName}</a>
                                            </Media>
 
                                            {experience.roles.map(function (role, i) {
                                                const startDate = moment(role.startDate);
                                                const timeEnd = moment(role.currentJob ? new Date() : new Date(role.endDate));
 
                                                return <div key={i}>
                                                    <h5>{role.title}</h5>
                                                    <span
                                                        className="jobDuration">{startDate.format('MMM YYYY')} - {role.currentJob ? 'Present' : timeEnd.format('MMM YYYY')}</span>
                                                    <span className="jobLocation">{role.location}</span>
                                                    <p className="jobDescription">{role.description}</p>
                                                </div>
                                            })}
                                        </Media>
                                    </Media>
                                </div>
                            );
                        })}
                    </Col>
                </Row>
            </Container>
        )
    }
}
 
export default ExperienceDetail;

src\components\experience-detail\ExperienceDetail.css

Example

.experienceContainer {
    padding: 30px 30px;
}
 
a:hover {
    text-decoration: none;
}
 
.profile-pic {
    height: 4vmin;
    margin-right: 10px;
    border-radius: 50px;
}
 
div.media {
    margin-top: 30px;
    margin-bottom: 50px;
}
 
div.media .media-heading .jobTotalDuration {
    color: #666;
    font-size: 14px;
    display: block;
}
 
div.media img {
    height: 18vmin;
    width: 18vmin;
    margin-right: 40px;
    border-radius: 50px;
}
 
div.media .jobDuration {
    color: #444;
    display: block;
}
 
div.media .jobLocation {
    color: #666;
}
 
.formLabel {
    font-weight: bold;
}
 
.certificateLink {
    color: cornflowerblue;
}
 
footer {
    margin-top: 20px;
    margin-bottom: 10px;
    border-top: #444444;
    text-align: center;
}
 
footer a {
    color: black;
    font-size: 30px;
    margin-right: 10px;
    margin-left: 10px;
}

src\components\skill-detail\SkillDetail.js

Example

import React, { Component } from 'react';
import ExpansionPanel from '@material-ui/core/ExpansionPanel';
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import Chip from '@material-ui/core/Chip';
import profile from '../../data/profile';
import StarIcon from '@material-ui/icons/Star';
import './SkillDetail.css';
 
const styles = {
    skillChip: 'skillChip',
    skillPanelDetail: 'skillPanelDetail'
}
 
class SkillDetail extends Component {
    render() {
        return (
            <div>
                {
                    profile.Skills.map((skill) => {
                        return (
                            <ExpansionPanel key={skill.Area} defaultExpanded>
                                <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
                                    <Typography variant="h5">{skill.Area}</Typography>
                                </ExpansionPanelSummary>
                                <ExpansionPanelDetails className={styles.skillPanelDetail}>
                                    {
                                        skill.SkillSet.map((skillDetail) => {
                                            return (
                                                <Chip
                                                    icon={skillDetail.Hot ? <StarIcon /> : null}
                                                    label={skillDetail.Name}
                                                    className={styles.skillChip}
                                                    color="primary"
                                                    key={skillDetail.Name}
                                                />
                                            );
                                        })
                                    }
                                </ExpansionPanelDetails>
                            </ExpansionPanel>
                        );
 
                    })
                }
 
            </div>
        );
    }
}
 
export default SkillDetail;

src\components\skill-detail\SkillDetail.css

Example

.skillChip {
    margin: 8px;
}
 
.skillChip span {
    font-weight: bold;
    font-size: 14px;
}
 
.tabPanelRoot .skillPanelDetail {
    display: inline-block;
}

The final folder structure

Example


Now launch the application
npm start

This will developed a static website on web server on port 3000 on your machine. This also launch your browser navigating to http://localhost:3000.

Most Helpful This Week