Tweaking the UI

So far we have published a Storefront and changed its appearance, using only a configuration file and some CLI generators. However, this leaves some unanswered questions:

  • The Storefront is not connected to anything. How do we hook something into the "buy" button?
  • The Storefront is one-size-fits-all. What if our customer wants more customizations?

We will introduce a more powerful customization mechanism, called UI Add-Ons. These are snippets of code that allow us to customize any part of the application's interface. In other words, we can change not only the appearance but the behavior of the application through custom code.

Changing the catalog view

You are introducing a new type of interface, specifically for quick service restaurants. Using this interface, customers only see the products Chickenshop offers but prices and option to add a product to cart are hidden because they will be known once users selects their preferences.

Let's image that our new cool interface will look something like this:

QSR HomeQSR Home

QSR Home

So, the first step is to check out the customer's project and create an add-on of type UI with the following command:

yalo addon:create -t ui

After running the command you will be prompted with two questions: name and version. These questions are important because they will identify your code and let you share it if you want to reuse it in future projects (more on this in ...).

██╗   ██╗ █████╗ ██╗      ██████╗      ██████╗██╗     ██╗
╚██╗ ██╔╝██╔══██╗██║     ██╔═══██╗    ██╔════╝██║     ██║
 ╚████╔╝ ███████║██║     ██║   ██║    ██║     ██║     ██║
  ╚██╔╝  ██╔══██║██║     ██║   ██║    ██║     ██║     ██║
   ██║   ██║  ██║███████╗╚██████╔╝    ╚██████╗███████╗██║
   ╚═╝   ╚═╝  ╚═╝╚══════╝ ╚═════╝      ╚═════╝╚══════╝╚═╝
                                                         

verifying credentials... done
verifying package... done (updating)
? Enter a name qsr
? Enter a version 0.1.0
creating "ui addon" structure... done
structure created successfully 🚀
copying static files [########################################] 100% | 2/2
Wrote to /Users/yalouser/path/to/project/packages/chickenshop/ui/package.json:

{
  "name": "qsr",
  "version": "0.1.0",
  "description": "Chickenshop ui addon - qsr",
  "main": "ui/storefront.plugin.ts",
  "scripts": {
    "test": "yalo preview"
  },
  "keywords": [],
  "author": "Yalo user",
  "license": "ISC"
}


Finish generating package.json
⸨░░░░░░░░░░░░░░░░░░⸩ ⠙ fetchMetadata: sill resolveWithNewModule 

+ [email protected]
+ [email protected]
+ @chakra-ui/[email protected]
+ @engyalo/[email protected]
+ @engyalo/[email protected]
added 180 packages from 151 contributors and audited 180 packages in 24.572s

10 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Install process... Install process finished

~ without fear of success!

📘

UI Addon creation

Keep in mind that a UI addon must have a unique name and version, if you enter an existing value, you will be asked to enter a different one. You can consider that if the name exists, a team mate may have created it before, so make sure you don't reinvent the wheel 😉

As the output of the command says, it created some boilerplate in your file system:

.
├── package-config.yml
└── ui/
    ├── package.json
    ├── plugins/
    └── storefront.plugins.ts

These files follow some convention so even though you can customize it, we recommend to follow the patterns :smiley:

  • ui - this folder contains all source code needed for a UI add-on.
  • ui/storefront.plugins.ts - this is a configuration file where you indicate what UI plugin (a React component that will be used in a specific place, i.e. product detail) belongs to what UI hook (a specific string used as a property of an object to know in which part of the UI will the plugin be used).
  • ui/plugins - this folder is expected to contain all your custom components (plugins).
  • package.json - this is used only to give you a better DX while coding, it contains the key deps used in storefront (TypeScript, Chakra UI, Storefront Types, Storefront Components). We try to use types as much as we can in order to make our code predictable and easier to understand, that's why we support TS but we have our own types that can be used for better code autocompletion and we also have some built-ins components that are based on Chakra UI.

As in the previous examples, this also adds a section to your package-config.yml:

name: chickenshop
region: us-east-1
storefront:
    region: us-east-1
    currency: MXN
    localization: es-MX
    enabledOrderNote: false
    config:
        -
            type: ui
            preset: qsr
            version: 0.1.0
            source: ./ui/storefront.plugins.ts

Here you can see the name (preset, think of babeljs conventions) and version you entered in addition to a couple of useful metadata fields for the CLI.

Now that you have the boilerplate ready to start customizing the UI, let's see what do we need in order to change the layout by using code. Open your editor of preference and go to the storefront.plugins.ts file, you will see something like this:

import { StorefrontPlugins } from '@engyalo/storefront-types'
// import { Grid } from '@chakra-ui/core'
// import { SomePlugin } from './plugins/SomePlugin'

export const plugins = {
  // Catalogue: {
  //     container: Grid,
  //     props: {
  //         gridTemplateColumns: '1fr',
  //     },
  // },
  // CollectionProduct: {
  //     container: SomePlugin,
  // },
} as StorefrontPlugins

The commented code is expected to guide you on how you can proceed to customize the UI. For example, in order to customize the layout, you can uncomment the Grid component import from @chakra-ui/core dependency and also uncomment the Catalogue hook settings (object). IT should look now something similar to this:

import { StorefrontPlugins } from '@engyalo/storefront-types'
import { Grid } from '@chakra-ui/core'
// import { SomePlugin } from './plugins/SomePlugin'

export const plugins = {
  Catalogue: {
      container: Grid,
      props: {
          gridTemplateColumns: '1fr',
      },
  },
  // CollectionProduct: {
  //     container: SomePlugin,
  // },
} as StorefrontPlugins

With these changes done, you can go to your terminal and run the preview command in order to see what has happened here:

yalo preview

This command will spin up a local development server with the base storefront and your custom changes applied:

UI Addon - changing layoutUI Addon - changing layout

UI Addon - changing layout

Let's examine what we have done in storefront.plugins.ts in order to get this output. Well, we have hooked into the Catalogue component and we have applied a custom container (think of a wrapper component) to be a Grid (from Chakra UI), and then we have defined some custom props for the container which in this case was to set the column templates of the grid to get only one column (you can check CSS Grid or the Chakra UI Grid component itself to better understand what gridColumnTemplates: '1fr' means). Isn't that cool? You didn't see the code but now you know that you can play with it without knowing what's behind the scenes :cool:

Ok, that was easy, but most of the work was done by Chakra UI because it's a common component. Now, what if we want to change not only the layout, instead what data and how it's displayed in the UI. For those cases, what we need is to create a custom component (or plugin as we call it internally). For that reason you can create a component inside plugins folder, you can follow the convention for folder structuring that works best for you, the only requirement is that it must be inside plugins folder (as mentioned above, it can be changed but it's better to follow conventions and don't affect configurations :wink: ). Let's assume that we're going to create just a component file with the name HorizontalProduct.tsx (remember that it should use TypeScript).

import React from 'react';
import { ICollectionProduct } from '@engyalo/storefront-types';

export const HorizontalProduct({
  product,
  currency,
  currencyPrefix,
  cart,
  ...props
}: ICollectionProduct) => {
  return null;
}

As you can see, by importing the props interface from Storefront Types package, you can get a better idea of what props are available for this plugin, this is very important because it won't work if you don't use the right props. Inside the component you can do whatever you want, create your custom component state, add functions and event handlers, render nested components, the decision is yours but be responsible :sweat-smile:

In our case, we want to use some product data available in product prop and rendering with some Chakra UI components to customize its layout:

import React from 'react';
import { ICollectionProduct } from '@engyalo/storefront-types';
import { ImageBox } from '@engyalo/storefront-components';
import { Grid, Box, Heading, Text } from '@chakra-ui/core';

export conts HorizontalProduct({
  product,
  currency,
  currencyPrefix,
  cart,
  ...props
}: ICollectionProduct) => {
  return (
    <Grid gridTemplateColumns="1fr 3fr" borderY="1px solid #F1F1F1" py="10px">
      <Box>
        <ImageBox imageUrl={product?.imageURL?.[0] ?? ''} />
      </Box>
      <Box>
        <Heading as="h3" size="sm">
          {product.name}
        </Heading>
        <Text fontFamily="body" color="gray.100" my="2">
          {product.description}
        </Text>
        <Text fontFamily="body" fontSize="md" color="primary.500">
          {currencyPrefix} {product.price} {currency}
        </Text>
      </Box>
    </Grid>
  );
}

📘

ESNext features support

You may be wondered about why we have to use optional chaining for accessing the image and it may look weird or even verbose but the main reason is that based on the schema used across the storefront application the imageURL is not a required field for all products, so TS forces us to supply some way of making sure that the <ImageBox /> component will receive a string type in the imageUrl prop, that's why we use optional chaining in this example, but feel free to implement your solution (adding if statements, using short circuit operators, ensuring that you will always get an image, etc).

Now, with your component ready, let's import it in our configuration file (storefront.plugins.ts) and assign it as a container for the CollectionProduct hook:

import { StorefrontPlugins } from '@engyalo/storefront-types'
import { Grid } from '@chakra-ui/core'
import { HorizontalProduct } from './plugins/HorizontalProduct'

export const plugins = {
  Catalogue: {
      container: Grid,
      props: {
          gridTemplateColumns: '1fr',
      },
  },
  CollectionProduct: {
      container: HorizontalProduct,
  },
} as StorefrontPlugins

Save your changes and if you have already running your preview command, you will see that we support hot reloading and your browser will be refreshed and look similar to:

Customized catalogue viewCustomized catalogue view

Customized catalogue view

With these changes we are done with our custom catalogue/home view. Hope you're excited to explore what else you can hook into to customize either its layout or behavior and if you find something that you can't customize yet because of a missing hook or plugin prop, let the product team know about it to see what's the best approach to tackle that use case :rocket: .


Did this page help you?