ZEMOSO ENGINEERING STUDIO
November 15, 2020
6 min read

Transforming product engineering with atomic design and a theming library — Part 2

In Part 1, we discussed the basics of atomic design and theming library, and how using them correctly can play a crucial role in product development, no matter what kind of project you are working on. 

In this article, we will explore how we can use atoms to build ‌molecules with basic functionality and event handling mechanisms.

Continue building the application

List item molecule
 List item molecule

We will use typography atoms of three different variants and create a list item molecule. We will also start looking into how to use data models for data coming from the back-end (or stored locally) to populate these values.

We will start adding padding, shadows, border radius, etc., but no specific styling.


import React from "react";

import PropTypes from "prop-types";


import { makeStyles } from "@material-ui/core/styles";

import Container from "@material-ui/core/Container";

import Box from "@material-ui/core/Box";


import Typography from "components/atoms/Typography";


const useStyle = makeStyles((theme) => ({

 root: {

   backgroundColor: theme.container.secondary.color,

   borderRadius: theme.container.secondary.borderRadius,

   padding: theme.container.secondary.padding,

 },

}));


const EventListItemComponent = (props) => {

 const { eventData } = props;

 const style = useStyle();


 return (

   <React.Fragment>

     <Box component={Container} boxShadow={2} className={style.root}>

       <Typography variant="header">{eventData.date}</Typography>

       <Typography variant="title">{eventData.title}</Typography>

       <Typography variant="subtitle">{eventData.type}</Typography>

     </Box>

   </React.Fragment>

 );

};


EventListItemComponent.propTypes = {

 eventData: PropTypes.shape({

   date: PropTypes.string.isRequired,

   title: PropTypes.string.isRequired,

   type: PropTypes.string.isRequired,

 }),

};



EventListItemComponent.defaultProps = {};


export default EventListItemComponent;


EventListItemComponent.js hosted with ❤ by GitHub


import { createMuiTheme } from "@material-ui/core/styles";


import { grey } from "@material-ui/core/colors";


const defaultTheme = createMuiTheme();


const baseTheme = createMuiTheme({

 container: {

   secondary: {

     color: grey[300],

     borderRadius: defaultTheme.spacing(1),

     padding: defaultTheme.spacing(2),

   },

 },

});


export default baseTheme;


Themes.js hosted with ❤ by GitHub

 Actual output from storybook
Actual output from storybook

In the above code, we used a special component from Material User Interface (UI) called Box. This component acts as a wrapper to handle most of the (Cascading Style Sheet) CSS styling like BoxShadow as part of the theme.

Add event organism

Since the code from here on out is going to only get larger, we’re going to give the overview of only one organism. For the other organism, check out the full project here.

Add an event organism that has some input fields, buttons, and validation logic. The TextField component is updated to accommodate the date and time.

import React, { useCallback, useState } from "react";

import PropTypes from "prop-types";



import { makeStyles } from "@material-ui/core/styles";

import Container from "@material-ui/core/Container";

import Box from "@material-ui/core/Box";

import Grid from "@material-ui/core/Grid";



import Typography from "components/atoms/Typography";

import TextField from "components/atoms/TextField";

import Button from "components/atoms/Button";



const useStyle = makeStyles((theme) => ({}));



const AddEventBanner = (props) => {

 const validate = { props };

 const style = useStyle();



 const [fieldToValidate, setFieldValidation] = useState({

   title: false,

   date: false,

   type: false,

 });



 const [inputFieldValue, setFieldValue] = useState({

   title: "",

   description: "",

   date: "",

   type: "",

 });



 const validateField = useCallback(

   (field) => {

     const input = inputFieldValue[field];

     const currentValidationState = fieldToValidate;

     if (input === null || input === "") {

       currentValidationState[field] = true;

     } else {

       currentValidationState[field] = false;

     }

     setFieldValidation({ ...currentValidationState });

     return !currentValidationState[field];

   },

   [inputFieldValue, fieldToValidate, setFieldValidation]

 );



 const isValid = useCallback(() => {

   let validationStatus = true;

   Object.keys(fieldToValidate).map((key, value) => {

     validationStatus = validationStatus && validateField(key);

     console.log(validationStatus);

   });

   return validationStatus;

 }, [fieldToValidate, validateField]);



 const handleFieldChange = useCallback(

   (field, value) => {

     const currentValidationState = fieldToValidate;

     currentValidationState[field] = false;

     setFieldValidation({ ...currentValidationState });



     const currentFieldValues = inputFieldValue;

     currentFieldValues[field] = value;

     setFieldValue({ ...currentFieldValues });

   },

   [fieldToValidate, inputFieldValue, setFieldValidation, setFieldValue]

 );



 const handleSubmit = useCallback(() => {

   if (!props.validate || (props.validate && isValid())) {

     props.onSubmit(inputFieldValue);

   }

 }, [isValid]);



 return (

   <Box component={Container}>

     <Grid container spacing={2}>

       <Grid item>

         <Typography variant="header">Add New Event</Typography>

       </Grid>

       <Grid item xs={12}>

         <TextField

           required

           label="Title"

           onChange={(event) => handleFieldChange("title", event.target.value)}

           error={fieldToValidate.title}

           onBlur={() => validate && validateField("title")}

         />

       </Grid>

       <Grid item xs={12}>

         <TextField

           label="Description"

           onChange={(event) =>

             handleFieldChange("description", event.target.value)

           }

         />

       </Grid>

       <Grid item xs={6}>

         <TextField

           required

           label="Date and Time"

           onChange={(event) => handleFieldChange("date", event.target.value)}

           type="datetime-local"

           error={fieldToValidate.date}

           onBlur={() => validate && validateField("date")}

         />

       </Grid>

       <Grid item xs={6}>

         <TextField

           required

           label="Event Type"

           onChange={(event) => handleFieldChange("type", event.target.value)}

           error={fieldToValidate.type}

           onBlur={() => validate && validateField("type")}

         />

       </Grid>

       <Grid item container xs={12} justify="center">

         <Button title="Add Event" onClick={handleSubmit} />

       </Grid>

     </Grid>

   </Box>

 );

};



AddEventBanner.propTypes = {

 validate: PropTypes.bool,

 onSubmit: PropTypes.func,

};



AddEventBanner.defaultProps = {

 validate: false,

};



export default AddEventBanner;

AddEvent.js hosted with ❤ by GitHub

Some basic validations and listeners, but no business logic yet. This is very important to create a boundary of what your component should do and how much it should control itself. For example, irrespective of where we add this component to the home screen or create a dedicated screen for events, it will be able to validate (or not validate) the required fields, and return the data to the main page for further processing. There are no network calls being made or any interactions with any data source. 

Add new event

Designing a home template

import React from "react";

import PropTypes from "prop-types";



import { makeStyles } from "@material-ui/core/styles";

import Paper from "@material-ui/core/Paper";

import Grid from "@material-ui/core/Grid";



const useStyle = makeStyles((theme) => ({

 root: {

   flexGrow: 1,

   backgroundColor: "#f00",

 },

 paper: {

   padding: theme.spacing(2),

 },

 container: {

   height: "100vh",

 },

 sideNav: {},

 banner: {},

 content: {},

 rightPanel: {},

}));



const HomeTemplate = (props) => {

 const style = useStyle();



 return (

   <div className={style.root}>

     <Paper className={style.paper}>

       <Grid container className={style.container} spacing={2}>

         <Grid item xs={2} className={style.sideNav}>

           {props.sideNav}

         </Grid>

         <Grid item container xs alignContent="flex-start" spacing={2}>

           <Grid item xs={12} className={style.banner}>

             {props.banner}

           </Grid>

           <Grid item xs={12} className={style.content}>

             {props.content}

           </Grid>

         </Grid>

         <Grid item xs={3} className={style.rightPanel}>

           {props.rightPanel}

         </Grid>

       </Grid>

     </Paper>

   </div>

 );

};



HomeTemplate.propTypes = {

 sideNav: PropTypes.element.isRequired,

 rightPanel: PropTypes.element.isRequired,

 banner: PropTypes.element.isRequired,

 content: PropTypes.element.isRequired,

};



HomeTemplate.defaultProps = {};



export default HomeTemplate;

HomeTemplate.js hosted with ❤ by GitHub

While many developers jump into divs for building the UI, we use Grids. They allow flexibility and rigidness at the same time. We can build an amazing responsive UI with minimal effort. 

The final home page

import React, { useEffect, useCallback, useState } from "react";


import HomeTemplate from "components/templates/HomeTemplate";


import NextEventBanner from "components/organisms/NextEventBanner";

import EventList from "components/organisms/EventList";

import AddEvent from "components/organisms/AddEvent";



import { fetchEventData } from "services/EventDataService";



const DummyView = () => (

 <div style={{ backgroundColor: "#adadad", height: "100vh" }}></div>

);



const HomePage = () => {

 const [eventDataList, setEventDataList] = useState([]);



 const fetchEventList = useCallback(async () => {

   const eventData = await fetchEventData();

   setEventDataList([...eventData]);

 }, [setEventDataList]);



 const handleAddEvent = useCallback((eventData) => {

   console.log("New Event Added", eventData);

 }, []);



 useEffect(() => {

   fetchEventList();

 }, [fetchEventList]);



 return (

   <HomeTemplate

     sideNav={<DummyView />}

     banner={

       <NextEventBanner

         date={eventDataList.length > 0 ? eventDataList[0].date : ""}

         title={eventDataList.length > 0 ? eventDataList[0].title : ""}

         description={

           eventDataList.length > 0 ? eventDataList[0].description : ""

         }

         type={eventDataList.length > 0 ? eventDataList[0].type : ""}

       />

     }

     rightPanel={

       <EventList title="Upcoming Event" eventDataList={eventDataList} />

     }

     content={<AddEvent validate onSubmit={handleAddEvent} />}

   />

 );

};



HomePage.propTypes = {};



HomePage.defaultProps = {};



export default HomePage;

HomePage.js hosted with ❤ by GitHub

Once all the setup is done, creating a page is really fast. Check out the code below.

First draft of home page
First draft of home page

In the above image, we are using a service to fetch and update the data.

import { fetchEventData } from "services/EventDataService";

const fetchEventList = useCallback(async () => {

   const eventData = await fetchEventData();

   setEventDataList([...eventData]);

}, [setEventDataList]);

Atomic design or not, this recommended practice to isolate data fetching, data processing, and data display keeps the app clean. 

Updating colors

Let us pick some beautiful colors and set up the palette for the application. One best place to begin Theming the application is to use Google’s Material Color Tool

Chosen color palette
Chosen color palette

Here I picked the following 

   ●   Primary : cyan[200]

   ●   Secondary: orange[500]

Material Color Tool offers a variety of previews and customizations to pick from. 

Adding color to our application:

const baseTheme = createMuiTheme({

 ...

 palette: {

   primary: {

     main: cyan[200],

   },

   secondary: {

     main: orange[500],

   },

 },

});

Updating the fonts

typography: {

   fontSize: 12,

},

UI with updated colors and font
UI with updated colors and font

When done right, the whole application’s appearance can be controlled from one file. Even if your application has 1000’s of pages, as long as you define some components, stick to a basic theme, and link it to a universal base theme: updates, upgrades, and modifications to your product appearance will be easy.

Deploying the application

You can learn more about deploying the application using Firebase here. 


Firebase: https://mohammed-atif.medium.com/deploy-react-application-to-firebase-using-github-actions-2c7514fba386

Bonus section: The dark theme

Let us make our page dark theme compatible

palette: {

   type: "dark",

   primary: {

     main: cyan[200],

   },

   secondary: {

     main: orange[500],

   },

   containerPrimary: {

     main: cyan[200],

     light: "#b4ffff",

     dark: "#4bacb8",

   },

   containerSecondary: {

     main: grey[400],

     light: "#f5f5f5",

     dark: "#373737",

   },

 },

That's it, just add type:dark, some minor tweaks and we are up and running.

 Light and Dark theme
Light and Dark theme

An earlier version of this blog post was published by the author on Medium. 

ZEMOSO ENGINEERING STUDIO

Transforming product engineering with atomic design and a theming library — Part 2

November 15, 2020
6 min read

In Part 1, we discussed the basics of atomic design and theming library, and how using them correctly can play a crucial role in product development, no matter what kind of project you are working on. 

In this article, we will explore how we can use atoms to build ‌molecules with basic functionality and event handling mechanisms.

Continue building the application

List item molecule
 List item molecule

We will use typography atoms of three different variants and create a list item molecule. We will also start looking into how to use data models for data coming from the back-end (or stored locally) to populate these values.

We will start adding padding, shadows, border radius, etc., but no specific styling.


import React from "react";

import PropTypes from "prop-types";


import { makeStyles } from "@material-ui/core/styles";

import Container from "@material-ui/core/Container";

import Box from "@material-ui/core/Box";


import Typography from "components/atoms/Typography";


const useStyle = makeStyles((theme) => ({

 root: {

   backgroundColor: theme.container.secondary.color,

   borderRadius: theme.container.secondary.borderRadius,

   padding: theme.container.secondary.padding,

 },

}));


const EventListItemComponent = (props) => {

 const { eventData } = props;

 const style = useStyle();


 return (

   <React.Fragment>

     <Box component={Container} boxShadow={2} className={style.root}>

       <Typography variant="header">{eventData.date}</Typography>

       <Typography variant="title">{eventData.title}</Typography>

       <Typography variant="subtitle">{eventData.type}</Typography>

     </Box>

   </React.Fragment>

 );

};


EventListItemComponent.propTypes = {

 eventData: PropTypes.shape({

   date: PropTypes.string.isRequired,

   title: PropTypes.string.isRequired,

   type: PropTypes.string.isRequired,

 }),

};



EventListItemComponent.defaultProps = {};


export default EventListItemComponent;


EventListItemComponent.js hosted with ❤ by GitHub


import { createMuiTheme } from "@material-ui/core/styles";


import { grey } from "@material-ui/core/colors";


const defaultTheme = createMuiTheme();


const baseTheme = createMuiTheme({

 container: {

   secondary: {

     color: grey[300],

     borderRadius: defaultTheme.spacing(1),

     padding: defaultTheme.spacing(2),

   },

 },

});


export default baseTheme;


Themes.js hosted with ❤ by GitHub

 Actual output from storybook
Actual output from storybook

In the above code, we used a special component from Material User Interface (UI) called Box. This component acts as a wrapper to handle most of the (Cascading Style Sheet) CSS styling like BoxShadow as part of the theme.

Add event organism

Since the code from here on out is going to only get larger, we’re going to give the overview of only one organism. For the other organism, check out the full project here.

Add an event organism that has some input fields, buttons, and validation logic. The TextField component is updated to accommodate the date and time.

import React, { useCallback, useState } from "react";

import PropTypes from "prop-types";



import { makeStyles } from "@material-ui/core/styles";

import Container from "@material-ui/core/Container";

import Box from "@material-ui/core/Box";

import Grid from "@material-ui/core/Grid";



import Typography from "components/atoms/Typography";

import TextField from "components/atoms/TextField";

import Button from "components/atoms/Button";



const useStyle = makeStyles((theme) => ({}));



const AddEventBanner = (props) => {

 const validate = { props };

 const style = useStyle();



 const [fieldToValidate, setFieldValidation] = useState({

   title: false,

   date: false,

   type: false,

 });



 const [inputFieldValue, setFieldValue] = useState({

   title: "",

   description: "",

   date: "",

   type: "",

 });



 const validateField = useCallback(

   (field) => {

     const input = inputFieldValue[field];

     const currentValidationState = fieldToValidate;

     if (input === null || input === "") {

       currentValidationState[field] = true;

     } else {

       currentValidationState[field] = false;

     }

     setFieldValidation({ ...currentValidationState });

     return !currentValidationState[field];

   },

   [inputFieldValue, fieldToValidate, setFieldValidation]

 );



 const isValid = useCallback(() => {

   let validationStatus = true;

   Object.keys(fieldToValidate).map((key, value) => {

     validationStatus = validationStatus && validateField(key);

     console.log(validationStatus);

   });

   return validationStatus;

 }, [fieldToValidate, validateField]);



 const handleFieldChange = useCallback(

   (field, value) => {

     const currentValidationState = fieldToValidate;

     currentValidationState[field] = false;

     setFieldValidation({ ...currentValidationState });



     const currentFieldValues = inputFieldValue;

     currentFieldValues[field] = value;

     setFieldValue({ ...currentFieldValues });

   },

   [fieldToValidate, inputFieldValue, setFieldValidation, setFieldValue]

 );



 const handleSubmit = useCallback(() => {

   if (!props.validate || (props.validate && isValid())) {

     props.onSubmit(inputFieldValue);

   }

 }, [isValid]);



 return (

   <Box component={Container}>

     <Grid container spacing={2}>

       <Grid item>

         <Typography variant="header">Add New Event</Typography>

       </Grid>

       <Grid item xs={12}>

         <TextField

           required

           label="Title"

           onChange={(event) => handleFieldChange("title", event.target.value)}

           error={fieldToValidate.title}

           onBlur={() => validate && validateField("title")}

         />

       </Grid>

       <Grid item xs={12}>

         <TextField

           label="Description"

           onChange={(event) =>

             handleFieldChange("description", event.target.value)

           }

         />

       </Grid>

       <Grid item xs={6}>

         <TextField

           required

           label="Date and Time"

           onChange={(event) => handleFieldChange("date", event.target.value)}

           type="datetime-local"

           error={fieldToValidate.date}

           onBlur={() => validate && validateField("date")}

         />

       </Grid>

       <Grid item xs={6}>

         <TextField

           required

           label="Event Type"

           onChange={(event) => handleFieldChange("type", event.target.value)}

           error={fieldToValidate.type}

           onBlur={() => validate && validateField("type")}

         />

       </Grid>

       <Grid item container xs={12} justify="center">

         <Button title="Add Event" onClick={handleSubmit} />

       </Grid>

     </Grid>

   </Box>

 );

};



AddEventBanner.propTypes = {

 validate: PropTypes.bool,

 onSubmit: PropTypes.func,

};



AddEventBanner.defaultProps = {

 validate: false,

};



export default AddEventBanner;

AddEvent.js hosted with ❤ by GitHub

Some basic validations and listeners, but no business logic yet. This is very important to create a boundary of what your component should do and how much it should control itself. For example, irrespective of where we add this component to the home screen or create a dedicated screen for events, it will be able to validate (or not validate) the required fields, and return the data to the main page for further processing. There are no network calls being made or any interactions with any data source. 

Add new event

Designing a home template

import React from "react";

import PropTypes from "prop-types";



import { makeStyles } from "@material-ui/core/styles";

import Paper from "@material-ui/core/Paper";

import Grid from "@material-ui/core/Grid";



const useStyle = makeStyles((theme) => ({

 root: {

   flexGrow: 1,

   backgroundColor: "#f00",

 },

 paper: {

   padding: theme.spacing(2),

 },

 container: {

   height: "100vh",

 },

 sideNav: {},

 banner: {},

 content: {},

 rightPanel: {},

}));



const HomeTemplate = (props) => {

 const style = useStyle();



 return (

   <div className={style.root}>

     <Paper className={style.paper}>

       <Grid container className={style.container} spacing={2}>

         <Grid item xs={2} className={style.sideNav}>

           {props.sideNav}

         </Grid>

         <Grid item container xs alignContent="flex-start" spacing={2}>

           <Grid item xs={12} className={style.banner}>

             {props.banner}

           </Grid>

           <Grid item xs={12} className={style.content}>

             {props.content}

           </Grid>

         </Grid>

         <Grid item xs={3} className={style.rightPanel}>

           {props.rightPanel}

         </Grid>

       </Grid>

     </Paper>

   </div>

 );

};



HomeTemplate.propTypes = {

 sideNav: PropTypes.element.isRequired,

 rightPanel: PropTypes.element.isRequired,

 banner: PropTypes.element.isRequired,

 content: PropTypes.element.isRequired,

};



HomeTemplate.defaultProps = {};



export default HomeTemplate;

HomeTemplate.js hosted with ❤ by GitHub

While many developers jump into divs for building the UI, we use Grids. They allow flexibility and rigidness at the same time. We can build an amazing responsive UI with minimal effort. 

The final home page

import React, { useEffect, useCallback, useState } from "react";


import HomeTemplate from "components/templates/HomeTemplate";


import NextEventBanner from "components/organisms/NextEventBanner";

import EventList from "components/organisms/EventList";

import AddEvent from "components/organisms/AddEvent";



import { fetchEventData } from "services/EventDataService";



const DummyView = () => (

 <div style={{ backgroundColor: "#adadad", height: "100vh" }}></div>

);



const HomePage = () => {

 const [eventDataList, setEventDataList] = useState([]);



 const fetchEventList = useCallback(async () => {

   const eventData = await fetchEventData();

   setEventDataList([...eventData]);

 }, [setEventDataList]);



 const handleAddEvent = useCallback((eventData) => {

   console.log("New Event Added", eventData);

 }, []);



 useEffect(() => {

   fetchEventList();

 }, [fetchEventList]);



 return (

   <HomeTemplate

     sideNav={<DummyView />}

     banner={

       <NextEventBanner

         date={eventDataList.length > 0 ? eventDataList[0].date : ""}

         title={eventDataList.length > 0 ? eventDataList[0].title : ""}

         description={

           eventDataList.length > 0 ? eventDataList[0].description : ""

         }

         type={eventDataList.length > 0 ? eventDataList[0].type : ""}

       />

     }

     rightPanel={

       <EventList title="Upcoming Event" eventDataList={eventDataList} />

     }

     content={<AddEvent validate onSubmit={handleAddEvent} />}

   />

 );

};



HomePage.propTypes = {};



HomePage.defaultProps = {};



export default HomePage;

HomePage.js hosted with ❤ by GitHub

Once all the setup is done, creating a page is really fast. Check out the code below.

First draft of home page
First draft of home page

In the above image, we are using a service to fetch and update the data.

import { fetchEventData } from "services/EventDataService";

const fetchEventList = useCallback(async () => {

   const eventData = await fetchEventData();

   setEventDataList([...eventData]);

}, [setEventDataList]);

Atomic design or not, this recommended practice to isolate data fetching, data processing, and data display keeps the app clean. 

Updating colors

Let us pick some beautiful colors and set up the palette for the application. One best place to begin Theming the application is to use Google’s Material Color Tool

Chosen color palette
Chosen color palette

Here I picked the following 

   ●   Primary : cyan[200]

   ●   Secondary: orange[500]

Material Color Tool offers a variety of previews and customizations to pick from. 

Adding color to our application:

const baseTheme = createMuiTheme({

 ...

 palette: {

   primary: {

     main: cyan[200],

   },

   secondary: {

     main: orange[500],

   },

 },

});

Updating the fonts

typography: {

   fontSize: 12,

},

UI with updated colors and font
UI with updated colors and font

When done right, the whole application’s appearance can be controlled from one file. Even if your application has 1000’s of pages, as long as you define some components, stick to a basic theme, and link it to a universal base theme: updates, upgrades, and modifications to your product appearance will be easy.

Deploying the application

You can learn more about deploying the application using Firebase here. 


Firebase: https://mohammed-atif.medium.com/deploy-react-application-to-firebase-using-github-actions-2c7514fba386

Bonus section: The dark theme

Let us make our page dark theme compatible

palette: {

   type: "dark",

   primary: {

     main: cyan[200],

   },

   secondary: {

     main: orange[500],

   },

   containerPrimary: {

     main: cyan[200],

     light: "#b4ffff",

     dark: "#4bacb8",

   },

   containerSecondary: {

     main: grey[400],

     light: "#f5f5f5",

     dark: "#373737",

   },

 },

That's it, just add type:dark, some minor tweaks and we are up and running.

 Light and Dark theme
Light and Dark theme

An earlier version of this blog post was published by the author on Medium. 

Recent Publications
Zemoso’s next big move: Entering Europe with new offices open in London
Zemoso’s next big move: Entering Europe with new offices open in London
August 29, 2022
Beyond Methodologies: 5 engineering do’s for an agile product
Beyond Methodologies: 5 engineering do’s for an agile product
August 28, 2022
Docs, Guides, Resources: Getting developer microsites right in a product-led world
Docs, Guides, Resources: Getting developer microsites right in a product-led world
August 16, 2022
Drone Technology: The upswing of its usage and value realization in EnergyTech
Drone Technology: The upswing of its usage and value realization in EnergyTech
August 10, 2022
Winning first place at O'Reilly Media’s Architectural Katas — Spring 2022
Winning first place at O'Reilly Media’s Architectural Katas — Spring 2022
July 13, 2022
How to remix Amazon’s Working Backwards with Google’s Venture’s User Journey: The Dr. Strange Way
How to remix Amazon’s Working Backwards with Google’s Venture’s User Journey: The Dr. Strange Way
June 14, 2022
How we built a big data platform for a futuristic AgriTech product
How we built a big data platform for a futuristic AgriTech product
June 3, 2022
Zemoso Labs starts operations in Waterloo, Canada
Zemoso Labs starts operations in Waterloo, Canada
May 25, 2022
Zemoso’s next big move: Entering Europe with new offices open in London
Zemoso’s next big move: Entering Europe with new offices open in London
August 29, 2022
Beyond Methodologies: 5 engineering do’s for an agile product
Beyond Methodologies: 5 engineering do’s for an agile product
August 28, 2022
Docs, Guides, Resources: Getting developer microsites right in a product-led world
Docs, Guides, Resources: Getting developer microsites right in a product-led world
August 16, 2022
Drone Technology: The upswing of its usage and value realization in EnergyTech
Drone Technology: The upswing of its usage and value realization in EnergyTech
August 10, 2022
Winning first place at O'Reilly Media’s Architectural Katas — Spring 2022
Winning first place at O'Reilly Media’s Architectural Katas — Spring 2022
July 13, 2022
ZEMOSO ENGINEERING STUDIO
November 15, 2020
6 min read

Transforming product engineering with atomic design and a theming library — Part 2

In Part 1, we discussed the basics of atomic design and theming library, and how using them correctly can play a crucial role in product development, no matter what kind of project you are working on. 

In this article, we will explore how we can use atoms to build ‌molecules with basic functionality and event handling mechanisms.

Continue building the application

List item molecule
 List item molecule

We will use typography atoms of three different variants and create a list item molecule. We will also start looking into how to use data models for data coming from the back-end (or stored locally) to populate these values.

We will start adding padding, shadows, border radius, etc., but no specific styling.


import React from "react";

import PropTypes from "prop-types";


import { makeStyles } from "@material-ui/core/styles";

import Container from "@material-ui/core/Container";

import Box from "@material-ui/core/Box";


import Typography from "components/atoms/Typography";


const useStyle = makeStyles((theme) => ({

 root: {

   backgroundColor: theme.container.secondary.color,

   borderRadius: theme.container.secondary.borderRadius,

   padding: theme.container.secondary.padding,

 },

}));


const EventListItemComponent = (props) => {

 const { eventData } = props;

 const style = useStyle();


 return (

   <React.Fragment>

     <Box component={Container} boxShadow={2} className={style.root}>

       <Typography variant="header">{eventData.date}</Typography>

       <Typography variant="title">{eventData.title}</Typography>

       <Typography variant="subtitle">{eventData.type}</Typography>

     </Box>

   </React.Fragment>

 );

};


EventListItemComponent.propTypes = {

 eventData: PropTypes.shape({

   date: PropTypes.string.isRequired,

   title: PropTypes.string.isRequired,

   type: PropTypes.string.isRequired,

 }),

};



EventListItemComponent.defaultProps = {};


export default EventListItemComponent;


EventListItemComponent.js hosted with ❤ by GitHub


import { createMuiTheme } from "@material-ui/core/styles";


import { grey } from "@material-ui/core/colors";


const defaultTheme = createMuiTheme();


const baseTheme = createMuiTheme({

 container: {

   secondary: {

     color: grey[300],

     borderRadius: defaultTheme.spacing(1),

     padding: defaultTheme.spacing(2),

   },

 },

});


export default baseTheme;


Themes.js hosted with ❤ by GitHub

 Actual output from storybook
Actual output from storybook

In the above code, we used a special component from Material User Interface (UI) called Box. This component acts as a wrapper to handle most of the (Cascading Style Sheet) CSS styling like BoxShadow as part of the theme.

Add event organism

Since the code from here on out is going to only get larger, we’re going to give the overview of only one organism. For the other organism, check out the full project here.

Add an event organism that has some input fields, buttons, and validation logic. The TextField component is updated to accommodate the date and time.

import React, { useCallback, useState } from "react";

import PropTypes from "prop-types";



import { makeStyles } from "@material-ui/core/styles";

import Container from "@material-ui/core/Container";

import Box from "@material-ui/core/Box";

import Grid from "@material-ui/core/Grid";



import Typography from "components/atoms/Typography";

import TextField from "components/atoms/TextField";

import Button from "components/atoms/Button";



const useStyle = makeStyles((theme) => ({}));



const AddEventBanner = (props) => {

 const validate = { props };

 const style = useStyle();



 const [fieldToValidate, setFieldValidation] = useState({

   title: false,

   date: false,

   type: false,

 });



 const [inputFieldValue, setFieldValue] = useState({

   title: "",

   description: "",

   date: "",

   type: "",

 });



 const validateField = useCallback(

   (field) => {

     const input = inputFieldValue[field];

     const currentValidationState = fieldToValidate;

     if (input === null || input === "") {

       currentValidationState[field] = true;

     } else {

       currentValidationState[field] = false;

     }

     setFieldValidation({ ...currentValidationState });

     return !currentValidationState[field];

   },

   [inputFieldValue, fieldToValidate, setFieldValidation]

 );



 const isValid = useCallback(() => {

   let validationStatus = true;

   Object.keys(fieldToValidate).map((key, value) => {

     validationStatus = validationStatus && validateField(key);

     console.log(validationStatus);

   });

   return validationStatus;

 }, [fieldToValidate, validateField]);



 const handleFieldChange = useCallback(

   (field, value) => {

     const currentValidationState = fieldToValidate;

     currentValidationState[field] = false;

     setFieldValidation({ ...currentValidationState });



     const currentFieldValues = inputFieldValue;

     currentFieldValues[field] = value;

     setFieldValue({ ...currentFieldValues });

   },

   [fieldToValidate, inputFieldValue, setFieldValidation, setFieldValue]

 );



 const handleSubmit = useCallback(() => {

   if (!props.validate || (props.validate && isValid())) {

     props.onSubmit(inputFieldValue);

   }

 }, [isValid]);



 return (

   <Box component={Container}>

     <Grid container spacing={2}>

       <Grid item>

         <Typography variant="header">Add New Event</Typography>

       </Grid>

       <Grid item xs={12}>

         <TextField

           required

           label="Title"

           onChange={(event) => handleFieldChange("title", event.target.value)}

           error={fieldToValidate.title}

           onBlur={() => validate && validateField("title")}

         />

       </Grid>

       <Grid item xs={12}>

         <TextField

           label="Description"

           onChange={(event) =>

             handleFieldChange("description", event.target.value)

           }

         />

       </Grid>

       <Grid item xs={6}>

         <TextField

           required

           label="Date and Time"

           onChange={(event) => handleFieldChange("date", event.target.value)}

           type="datetime-local"

           error={fieldToValidate.date}

           onBlur={() => validate && validateField("date")}

         />

       </Grid>

       <Grid item xs={6}>

         <TextField

           required

           label="Event Type"

           onChange={(event) => handleFieldChange("type", event.target.value)}

           error={fieldToValidate.type}

           onBlur={() => validate && validateField("type")}

         />

       </Grid>

       <Grid item container xs={12} justify="center">

         <Button title="Add Event" onClick={handleSubmit} />

       </Grid>

     </Grid>

   </Box>

 );

};



AddEventBanner.propTypes = {

 validate: PropTypes.bool,

 onSubmit: PropTypes.func,

};



AddEventBanner.defaultProps = {

 validate: false,

};



export default AddEventBanner;

AddEvent.js hosted with ❤ by GitHub

Some basic validations and listeners, but no business logic yet. This is very important to create a boundary of what your component should do and how much it should control itself. For example, irrespective of where we add this component to the home screen or create a dedicated screen for events, it will be able to validate (or not validate) the required fields, and return the data to the main page for further processing. There are no network calls being made or any interactions with any data source. 

Add new event

Designing a home template

import React from "react";

import PropTypes from "prop-types";



import { makeStyles } from "@material-ui/core/styles";

import Paper from "@material-ui/core/Paper";

import Grid from "@material-ui/core/Grid";



const useStyle = makeStyles((theme) => ({

 root: {

   flexGrow: 1,

   backgroundColor: "#f00",

 },

 paper: {

   padding: theme.spacing(2),

 },

 container: {

   height: "100vh",

 },

 sideNav: {},

 banner: {},

 content: {},

 rightPanel: {},

}));



const HomeTemplate = (props) => {

 const style = useStyle();



 return (

   <div className={style.root}>

     <Paper className={style.paper}>

       <Grid container className={style.container} spacing={2}>

         <Grid item xs={2} className={style.sideNav}>

           {props.sideNav}

         </Grid>

         <Grid item container xs alignContent="flex-start" spacing={2}>

           <Grid item xs={12} className={style.banner}>

             {props.banner}

           </Grid>

           <Grid item xs={12} className={style.content}>

             {props.content}

           </Grid>

         </Grid>

         <Grid item xs={3} className={style.rightPanel}>

           {props.rightPanel}

         </Grid>

       </Grid>

     </Paper>

   </div>

 );

};



HomeTemplate.propTypes = {

 sideNav: PropTypes.element.isRequired,

 rightPanel: PropTypes.element.isRequired,

 banner: PropTypes.element.isRequired,

 content: PropTypes.element.isRequired,

};



HomeTemplate.defaultProps = {};



export default HomeTemplate;

HomeTemplate.js hosted with ❤ by GitHub

While many developers jump into divs for building the UI, we use Grids. They allow flexibility and rigidness at the same time. We can build an amazing responsive UI with minimal effort. 

The final home page

import React, { useEffect, useCallback, useState } from "react";


import HomeTemplate from "components/templates/HomeTemplate";


import NextEventBanner from "components/organisms/NextEventBanner";

import EventList from "components/organisms/EventList";

import AddEvent from "components/organisms/AddEvent";



import { fetchEventData } from "services/EventDataService";



const DummyView = () => (

 <div style={{ backgroundColor: "#adadad", height: "100vh" }}></div>

);



const HomePage = () => {

 const [eventDataList, setEventDataList] = useState([]);



 const fetchEventList = useCallback(async () => {

   const eventData = await fetchEventData();

   setEventDataList([...eventData]);

 }, [setEventDataList]);



 const handleAddEvent = useCallback((eventData) => {

   console.log("New Event Added", eventData);

 }, []);



 useEffect(() => {

   fetchEventList();

 }, [fetchEventList]);



 return (

   <HomeTemplate

     sideNav={<DummyView />}

     banner={

       <NextEventBanner

         date={eventDataList.length > 0 ? eventDataList[0].date : ""}

         title={eventDataList.length > 0 ? eventDataList[0].title : ""}

         description={

           eventDataList.length > 0 ? eventDataList[0].description : ""

         }

         type={eventDataList.length > 0 ? eventDataList[0].type : ""}

       />

     }

     rightPanel={

       <EventList title="Upcoming Event" eventDataList={eventDataList} />

     }

     content={<AddEvent validate onSubmit={handleAddEvent} />}

   />

 );

};



HomePage.propTypes = {};



HomePage.defaultProps = {};



export default HomePage;

HomePage.js hosted with ❤ by GitHub

Once all the setup is done, creating a page is really fast. Check out the code below.

First draft of home page
First draft of home page

In the above image, we are using a service to fetch and update the data.

import { fetchEventData } from "services/EventDataService";

const fetchEventList = useCallback(async () => {

   const eventData = await fetchEventData();

   setEventDataList([...eventData]);

}, [setEventDataList]);

Atomic design or not, this recommended practice to isolate data fetching, data processing, and data display keeps the app clean. 

Updating colors

Let us pick some beautiful colors and set up the palette for the application. One best place to begin Theming the application is to use Google’s Material Color Tool

Chosen color palette
Chosen color palette

Here I picked the following 

   ●   Primary : cyan[200]

   ●   Secondary: orange[500]

Material Color Tool offers a variety of previews and customizations to pick from. 

Adding color to our application:

const baseTheme = createMuiTheme({

 ...

 palette: {

   primary: {

     main: cyan[200],

   },

   secondary: {

     main: orange[500],

   },

 },

});

Updating the fonts

typography: {

   fontSize: 12,

},

UI with updated colors and font
UI with updated colors and font

When done right, the whole application’s appearance can be controlled from one file. Even if your application has 1000’s of pages, as long as you define some components, stick to a basic theme, and link it to a universal base theme: updates, upgrades, and modifications to your product appearance will be easy.

Deploying the application

You can learn more about deploying the application using Firebase here. 


Firebase: https://mohammed-atif.medium.com/deploy-react-application-to-firebase-using-github-actions-2c7514fba386

Bonus section: The dark theme

Let us make our page dark theme compatible

palette: {

   type: "dark",

   primary: {

     main: cyan[200],

   },

   secondary: {

     main: orange[500],

   },

   containerPrimary: {

     main: cyan[200],

     light: "#b4ffff",

     dark: "#4bacb8",

   },

   containerSecondary: {

     main: grey[400],

     light: "#f5f5f5",

     dark: "#373737",

   },

 },

That's it, just add type:dark, some minor tweaks and we are up and running.

 Light and Dark theme
Light and Dark theme

An earlier version of this blog post was published by the author on Medium. 

Recent Publications

ZEMOSO ENGINEERING STUDIO

Beyond Methodologies: 5 engineering do’s for an agile product

August 28, 2022
6 min read
ZEMOSO ENGINEERING STUDIO

How we built a big data platform for a futuristic AgriTech product

June 3, 2022
8 min read
ZEMOSO NEWS

Zemoso Labs starts operations in Waterloo, Canada

May 25, 2022
5 min read
ZEMOSO ENGINEERING STUDIO

Honorable mention at O’Reilly’s Architectural Katas event

May 17, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

Product dev with testable spring boot applications, from day one

May 4, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

When not to @Autowire in Spring or Spring Boot applications

May 1, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

Efficiently handle data and integrations in Spring Boot

January 24, 2021
5 min read
ZEMOSO ENGINEERING STUDIO

Our favorite CI/CD DevOps Practice: Simplify with GitHub Actions

October 25, 2020
5 min read
ZEMOSO ENGINEERING STUDIO

How to use BERT and DNN to build smarter NLP algorithms for products

February 14, 2020
12 min read
ZEMOSO ENGINEERING STUDIO

GraphQL with Java Spring Boot and Apollo Angular for Agile platforms

April 30, 2019
9 min read
ZEMOSO ENGINEERING STUDIO

GraphQL — Why is it essential for agile product development?

April 30, 2019
12 min read
ZEMOSO ENGINEERING STUDIO

Deploying Airflow on Kubernetes 

November 30, 2018
2 min read
ZEMOSO PRODUCT STUDIO

How to validate your Innovation: Mastering Experiment Design

November 22, 2018
8 min read
ZEMOSO PRODUCT STUDIO

Working Backwards: Amazon's Culture of Innovation: My notes

November 19, 2018
8 min read
ZEMOSO ENGINEERING STUDIO

Product developer POV: Caveats when building with Spark

November 5, 2018
2 min read

Want more best practices?

Access thought-leadership and best practice content across
the product development lifecycle

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

© 2021 Zemoso Technologies
Privacy Policy

Terms of Use
LinkedIn Page - Zemoso TechnologiesFacebook Page - Zemoso TechnologiesTwitter Account - Zemoso Technologies

© 2021 Zemoso Technologies
Privacy Policy

LinkedIn Page - Zemoso TechnologiesFacebook Page - Zemoso TechnologiesTwitter Account - Zemoso Technologies
September 19, 2022