Image Recognition
15/07/2021

Scalable Design system in React - AI in Retail Tech series

Welcome to another AI-in-Retail tech series!
At Infilect, we build Enterprise SaaS image recognition platforms to empower global retail decision-makers to do more when it comes to optimizing their in-store retail execution processes.

Think about a typical sales manager inside a Consumer Packaged Goods company such as P&G who has to make sure that the most essential products reach your nearby stores. A sales manager has to manage sales in thousands of stores across geography and make data-driven decisions to ensure consumers, like you and me, easily locate products on the store shelves.

Let us explore what happens behind the scene when we aim to provide leaders with the most accurate data & unparalleled product experience, so that they find insights & actions in a single place, at the right time? 

Towards answering such a question and creating uniform digital workflows in our product, InfiViz, we have built a scalable front-end and back-end system, with design as its core value.

Complex systems impede, Simple systems succeed. - Chip Averwater, Retail Truths, the unconventional wisdom of retailin
design system in react

With a uniform and scalable design system, our product helps retail decision-makers enjoy a customized and seamless experience to find the most relevant and critical data easily. To achieve this we bring ease of use and design at the core of our system; a consistent UI across the product and different experiences inside the product.

To build consistent UI across products and help users get familiar with the design language it is essential for any org to create a design system. It also guides engineering and design teams to build UI that represents the brand of the company. In this blog, we’ll cover the use of React with Theme UI to build a design system.

Firstly, let’s take a look at the folder structure

|-- src
  |-- theme
        |-- ThemeProvider.js
        |-- colors.js
        |-- index.js
  |-- ui-components
     |-- Button
        |-- Button.js
        |-- Button.test.js
        |-- style.js
     |-- Typography
        |-- Typography.js
        |-- Typography.test.js
        |-- style.js

Goals of the design system

  1. A set of React components and their properties that are accepted as props, which can be reused through the app.
  2. Self-documenting code, to make it easy for developers to use. We can use Storybook to provide a visual way to represent the components with their documentation.
  3. All components to have unit-tested. We can use the React Testing Library to test out our components.

Step 1 - Style rules

src/theme/colors.js

const colors = {
 dark: {
   primary: '#3cf',
   secondary: '#e0f',
   text: '#fff',
   background: '#191919',
 },
 light: {
   primary: '#30c',
   secondary: '#6100cc',
   text: '#000',
   background: '#fff',
 }
};

export default colors;

In the colors.js file, we define the color palette to be used in the app. We have defined colors for both light and dark themes and potentially add more colors in the file as we support multiple color themes.

  • src/theme/index.js

import colors from './colors'

const theme = {
 colors: {
   modes: {...colors}
 },
 text: {
   default: {
     color: 'text',
     fontSize: 3
   },
   caps: {
     textTransform: 'uppercase',
     letterSpacing: '1px'
   },
 },
 fonts: {
   body: '"Poppins", system-ui, sans-serif',
   heading: '"Avenir Next", sans-serif',
   monospace: 'Menlo, monospace'
 },
 buttons: {
   primary: {
     background: 'primary',
     backgroundSize: '210% 210%',
     backgroundPosition: '100% 0',
     backgroundColor: 'primary',
     fontSize: 13
   },
   outlined: {
     fontSize: 13,
     textTransform: 'uppercase',
     border: '3px solid',
     fontWeight: 700,
     borderRadius: 0,
     backgroundColor: 'transparent'
   }
 },
 radii: {
   '2p': 2,
   '5p': 5,
   '30p': 30,
   '50p': 50
 },
 sizes: {
   '10p': 10
 },
 links: {
   bold: {
     fontWeight: 'bold'
   },
   nav: {
     fontWeight: '700',
     color: 'text',
     textDecoration: 'none',
     bg: 'inherit',
     fontSize: 14
   }
 }
};

export default theme;

The index.js file defines the rules for the styling.

src/theme/withTheme.js

withTheme.js file exports a HOC that can be used to wrap any component with the Theme Provider which uses the rules that we created in the above index.js file.

import React from 'react';
import { ThemeProvider } from 'theme-ui';

import theme from './theme';

const withTheme = (
 WrappedComponent,
 config = { initialColorModeName: 'light' }
) => props => (
 <ThemeProvider
   theme={{ ...theme, initialColorModeName: config.initialColorModeName }} >
   <WrappedComponent {...props} {...theme} />
 </ThemeProvider>
);

export default withTheme;

Ideally, we wrap the App.js file or the entry point of the application with the withTheme HOC.

import React from 'react';
// theme
import withTheme from 'theme/withTheme';

function App() {
 return (
     <p> Hello from the App.js file </p>
 );
}

export default withTheme(App, { initialColorModeName: 'light' });

Now that the base of the theme is set up for the app let’s go ahead and create our actual UI components.

Step 2 - UI components

Building the UI components is a straightforward approach. It is similar to how we create any other functional components in React. Here we create wrappers over the components provided by Theme-UI and style them according to the rules and props.

src/Button/Button.js

import React from 'react';
import PropTypes from 'prop-types';
import { Button as ButtonComponent } from '@theme-ui/components';

const getHeightWidth = {
'sm': { width: 100, height: 40 },
'md': { width: 200, height: 60 },
}

const Button = ({ size, children, color, variant, ...rest }) => {
 const { width, height } = getHeightWidth[size];
 return (
   <ButtonComponent
     variant={variant}
     sx={{
       height,
       width,
       outline: 'none',
       color: 'text',
       background: 'color',
       ...(variant === 'outlined' && {
         background: 'transparent',
         border: '1px solid',
         color: 'color',
         '&:hover': {
           backgroundColor: 'color',
         }
       }),
     }}
     {...rest}
 
 >
     {children}
   </ButtonComponent>
 );
};

Button.propTypes = {
 /**
  * @type {string}
  * @description - Determines variant of button
  */
 variant: PropTypes.oneOf(['normal', outline', 'round', 'icon']),
 /**
  * @type {string}
  * @description - Determines size of button
  */
 size: PropTypes.oneOf(['sm', 'md', 'lg']),
 /**
  * @type {function | element}
  * @description - Children prop
  */
 children: PropTypes.oneOfType([
   PropTypes.node,
   PropTypes.element,
   PropTypes.string
 ]).isRequired
};

Button.defaultProps = {
 size: 'sm',
 variant: 'normal'
};

export default Button;

The Button component can be as simple as accepting just variant and size props but as needed more props can be passed to the component and styled.

Pro Tip: You can also use a library like classnames to apply conditional classes based on the props provided.  

src/Typography/Typography.js

import React from 'react';
import PropTypes from 'prop-types';
import { Text } from 'theme-ui';

const sizeMap = {
 'xs': 10,
 'sm': 15,
 'md': 20,
 'l': 25,
 'xl': 30,
 'xxl': 45,
}

/**
* @class
* @description Core Typography component
*/
const Typography = ({
 color,
 weight,
 size,
 children,
}) => {
 return (
     <Text
       sx={{
         color,
         fontWeight: weight,
         fontSize: sizeMap[size]
       }}
       {...rest}>
       {children}
     </Text>
 );
};

// component properties
Typography.propTypes = {
 /**
  * @type {string}
  * @description - Font color
  */
 color: PropTypes.string,
 /**
  * @type {string}
  * @description - Font size - 'xs' | 'sm' | 'md'| 'l' | 'xl' | 'xxl' |
  */
 size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 /**
  * @type {string}
  * @description - Font weight - 'light' | 'normal' | 'medium' | 'bold'| 'bolder' |
  */
 weight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 /**
  * @type {function | element}
  * @description - Child elements
  */
 children: PropTypes.oneOfType([
   PropTypes.func,
   PropTypes.element,
   PropTypes.node,
   PropTypes.string
 ]).isRequired
};

Typography.defaultProps = {
 color: 'text',
 size: 'sm',
 weight: 'normal',
};

export default Typography;

There are no strict rules to define component props. But those should be well-defined and developer-friendly. The props should be accounted for the possible variations and behaviors of the components.

The reason we designed a UI library on top of Theme UI is it is highly customizable and provides flexible APIs. There are a lot of other options like Styled Components, Tailwind CSS, Emotion, etc. which can be very well used to build the design system but the approach may differ based on the chosen library.

This rigorous practice of fusing best design practices with front-end engineering helps us bring a seamless product experience for Infiviz. 

At Infilect, we are hiring passionate and talented individuals to join our front-end engineering team. If building scalable and customizable front-end excites you, please apply here or get in touch with us through the below form. 

For more information, you can reach out to Cibi on LinkedIn