May 3, 2023

How to Customize Existing Volto Components by Component Shadowing?

As a developer, you may find yourself in a situation where you need to customize existing Volto components. While it’s tempting to modify the existing code directly, this approach can lead to problems down the road, such as conflicts with future updates. A better way to achieve customization is through component shadowing, which allows you to create a copy of the component you want to modify and modify it without affecting the original code.

In this blog post, we’ll walk you through the process of customizing existing Volto components by using component shadowing.

We’ll assume that you have a basic understanding of Volto and React development.

What is Component Shadowing?

Component shadowing is an elegant technique that enables you to override existing components in Volto with your own modified versions without altering the original components. This approach involves creating a new directory in your Volto project’s src/customizations directory, with the same path as the component being replaced. It allows you to create a copy of an existing component and make changes as needed while preserving the original component.

To implement component shadowing, you’ll need to restart Volto after adding a file to the customizations or theme folders. Once the changes are in effect, the page will automatically update, thanks to the hot reloading feature. In addition to components, Volto also enables you to customize other modules, such as actions and reducers.

Volto’s source code is located in the /omelette/ directory. This is where you can access the underlying code for Volto and make modifications or additions as needed. By having access to the code, you can tailor the Volto platform to your specific needs and preferences, allowing for greater flexibility and customization options. With this level of control, you can fine-tune the performance of your Volto site and ensure it aligns perfectly with your project requirements.

React Developer Tools: Finding Components to Override

React Developer Tools is a browser extension that helps developers debug and inspect React applications. One of its useful features is the component selector, which allows developers to identify the specific component they need to override.

To access the component selector, open the React Developer Tools panel in your browser’s developer tools. From there, click on the “Select a component on the page” button and then click on the component you want to inspect. The component’s name and props will appear in the panel.

Overall, the React Developer Tools can be a powerful aid for React developers in debugging and modifying their applications.

Header component

Here, we are changing the style of the header by the component shadowing method.

We’ll do this by cloning the original Volto Header component from the omelette folder. omelette/src/components/theme/Header/Header.jsx into

src/customizations/components/theme/Header/Header.jsx.

Header.jsx

/**
* Header component.
* @module components/theme/Header/Header
*/

import React, { Component } from 'react';
import { Container, Segment } from 'semantic-ui-react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// imported custom styles
import './styles.css';

import {
 Anontools,
 LanguageSelector,
 Logo,
 Navigation,
 SearchWidget,
} from '@plone/volto/components';

/**
* Header component class.
* @class Header
* @extends Component
*/
class Header extends Component {
 /**
  * Property types.
  * @property {Object} propTypes Property types.
  * @static
  */
 static propTypes = {
   token: PropTypes.string,
   pathname: PropTypes.string.isRequired,
 };

 /**
  * Default properties.
  * @property {Object} defaultProps Default properties.
  * @static
  */
 static defaultProps = {
   token: null,
 };

 /**
  * Render method.
  * @method render
  * @returns {string} Markup for the component.
  */
 render() {
   return (
     <Segment basic className="header-wrapper" role="banner">
       <Container>
         <div className="header">
           <div className="logo-nav-wrapper">
             <div className="logo">
               <Logo />
             </div>
             {/* Replaced The Navigation Component with Search Component */}
             <SearchWidget />
           </div>
           <div className="tools-search-wrapper">
             <LanguageSelector />
             {!this.props.token && (
               <div className="tools">
                 <Anontools />
               </div>
             )}
             <div className="search">
               {/* Replaced The Search Component with Navigation Component */}
             <Navigation pathname={this.props.pathname} />
             </div>
           </div>
         </div>
       </Container>
     </Segment>
   );
 }
}

export default connect((state) => ({
 token: state.userSession.token,
}))(Header);

styles.css

.ui.basic.segment.header-wrapper{
   background: #1a1414;

}

.ui.secondary.pointing.menu .active.item {
   color: #ffffff !important;
}

.ui.secondary.pointing.menu .dropdown.item:hover, .ui.secondary.pointing.menu .link.item:hover, .ui.secondary.pointing.menu a.item:hover {
   background-color: #ffffff00;
   color: rgb(227 227 227 / 87%) !important;
}

.ui.basic.segment .header .logo-nav-wrapper {
  gap: 20px;
}

This is the customized Header

The Footer

Here, we are changing the style of the footer by the component shadowing method.

To personalize the footer of your website, you can duplicate the file “Footer.jsx” from the omelette/src/components/theme/Footer/Footer.jsx directory and save it to your customization folder at customizations/components/theme/Footer/Footer.jsx. Once you restart your website, you can modify the Footer component to your liking, and the changes will be displayed thanks to hot reloading instantly.

The updated Footer.jsx

import React from 'react';
import './Footer.css';

const Footer = () => {
 return (
   <div className="footer">
     <div className="footer-content">
       <h2>Contact Us</h2>
       <p>123 Main Street</p>
       <p>New York, NY 10001</p>
       <p>Phone: (555) 555-5555</p>
       <p>Email: info@mywebsite.com</p>
     </div>
     <div className="footer-content">
       <h2>About Us</h2>
       <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sodales auctor nisi, in scelerisque odio ultricies nec. Donec interdum euismod elit.</p>
     </div>
     <div className="footer-content">
       <h2>Follow Us</h2>
       <ul className="social-links">
         <li><a href="#"><i className="fab fa-facebook"></i></a></li>
         <li><a href="#"><i className="fab fa-twitter"></i></a></li>
         <li><a href="#"><i className="fab fa-instagram"></i></a></li>
       </ul>
     </div>
   </div>
 );
};

export default Footer;

Footer.css

.footer {
   display: flex;
   justify-content: space-between;
   align-items: center;
   height: 200px;
   background-color: #191919;
   color: #fff;
   bottom: 0;
   width: 100%;
   font-size: 14px;
 }
  .footer-content {
   flex: 1;
   margin: 20px;
 }
  .footer-content h2 {
   margin-bottom: 20px;
   color: #fff;
   font-size: 18px;
 }
  .footer-content p {
   margin-bottom: 10px;
   color: #ccc;
 }
  .social-links {
   list-style: none;
   padding: 0;
   display: flex;
 }
  .social-links li {
   margin-right: 10px;
 }
  .social-links li:last-child {
   margin-right: 0;
 }
  .social-links a {
   color: #ccc;
   text-decoration: none;
   font-size: 24px;
 }
  .social-links a:hover {
   color: #fff;
 }

This is the customized Footer

The File Item View

The FileView component is responsible for displaying information about a file. We will improve the view by Showing the file type, size, creator, and last modified date, and style the component using semantic-ui-react.

This information is not shown by default. So you need to customize the way a FileView is rendered.
The Volto component to render a FileView is in :

/omelette/src/components/theme/View/FileView.jsx

We’ll do this by cloning the original Volto Header component from the omelette folder. /omelette/src/components/theme/View/FileView.jsx into
src/customizations/components/theme/View/FileView.jsx.

The updated FileView.jsx

import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Button, Header, Segment } from 'semantic-ui-react';
import { Container } from 'semantic-ui-react';
import { flattenToAppURL } from '@plone/volto/helpers';

const FileView = ({ content }) => (
 <>
   <Segment secondary textAlign={'center'}>
     <Header>
       <h1 className="documentFirstHeading">
         {content.title}
         {content.subtitle && ` - ${content.subtitle}`}
       </h1>
     </Header>

    
     <svg
       xmlns="http://www.w3.org/2000/svg"
       width="100px"
       height="100px"
       viewBox="0 0 36 36"
     >
       <path
         fill-rule="evenodd"
         d="M6.9996,3.0003 L6.9996,33.0003 L29.0006,33.0003 L29.0006,11.5863 L20.4136,3.0003 L6.9996,3.0003 Z M9.0006,5.0003 L19.0006,5.0003 L19.0006,13.0003 L27.0006,13.0003 L27.0006,31.0003 L9.0006,31.0003 L9.0006,5.0003 Z M20.9996,6.4143 L25.5856,11.0003 L20.9996,11.0003 L20.9996,6.4143 Z"
       />
     </svg>
     <p>
       File Type: {content.file['content-type'].split('/').pop()}
       <br />
       File Size:{' '}
       {`${(
         content.file['size'] /
         1024 ** Math.floor(Math.log(content.file['size']) / Math.log(1024))
       ).toFixed(2)} ${
         ['B', 'KB', 'MB', 'GB'][
           Math.floor(Math.log(content.file['size']) / Math.log(1024))
         ]
       }`}{' '}
       <br />
       Created by - {content.creators}
       <br />
       Last Modified on File: {moment(content.modified).format('DD MMM, YYYY')}
     </p>

     {content.file?.download && (
       <>
         <Button href={flattenToAppURL(content.file.download)} primary>
           Download
         </Button>
       </>
     )}
   </Segment>
 </>
);

FileView.propTypes = {
 content: PropTypes.shape({
   title: PropTypes.string,
   description: PropTypes.string,
   file: PropTypes.shape({
     download: PropTypes.string,
     filename: PropTypes.string,
   }),
 }).isRequired,
};

export default FileView;
The customized FileView is
The Listing Block

The listing block can be used to display various types of content, such as news articles, events, or blogs, and can be customized in many ways to suit the needs of the site. To edit a listing block in Volto, the user can simply click on the block and access its editing options. These options include the ability to modify the query that retrieves the content items, select a display format, and configure filters or sorting options.

The Volto component to render a DefaultTemplate is in:

/omelette/src/components/manage/Blocks/Listing/DefaultTemplate.jsx

Copy omelette/src/components/manage/Blocks/Listing/DefaultTemplate.jsx to src/customizations/components/manage/Blocks/Listing/DefaultTemplate.jsx

Here, we are changing the style of the Listing Block (Default View) style by the component shadowing method, and we will add features that make the list of items on the webpage more informative, including metadata for each item, the use of semantic HTML elements for better accessibility, a FormattedDate component to display the effective date of each item, and a Container and Divider component to visually separate the list of items from the link to view more.

The updated DefaultTemplate.jsx

import React from 'react';
import PropTypes from 'prop-types';
import { ConditionalLink, UniversalLink } from '@plone/volto/components';
import { flattenToAppURL } from '@plone/volto/helpers';

import { isInternalURL } from '@plone/volto/helpers/Url/Url';

const DefaultTemplate = ({ items, linkTitle, linkHref, isEditMode }) => {
 let link = null;
 let href = linkHref?.[0]?.['@id'] || '';

 if (isInternalURL(href)) {
   link = (
     <ConditionalLink to={flattenToAppURL(href)} condition={!isEditMode}>
       {linkTitle || href}
     </ConditionalLink>
   );
 } else if (href) {
   link = <UniversalLink href={href}>{linkTitle || href}</UniversalLink>;
 }

 return (
   <>
     <div className="items">
       {items.map((item) => (
        
         <div className="listing-item" key={item['@id']}>
           <ConditionalLink item={item} condition={!isEditMode}>
             <div className="listing-body">
               <h4>{item.title ? item.title : item.id}</h4>
               <p>{item.description}</p>
             </div>
           </ConditionalLink>
         </div>
       ))}
     </div>

     {link && <div className="footer">{link}</div>}
   </>
 );
};
DefaultTemplate.propTypes = {
 items: PropTypes.arrayOf(PropTypes.any).isRequired,
 linkMore: PropTypes.any,
 isEditMode: PropTypes.bool,
};
export default DefaultTemplate;

The customized Default View is

In conclusion, component shadowing is a powerful technique that allows developers to customize existing Volto components without altering the original code. With this method, you can create a copy of an existing component and make changes to suit your project requirements, while still maintaining the original codebase.

In this blog post, we walked through the process of customizing several Volto components, such as the Header, Footer, FileView, and Listing Block, by using the component shadowing method. We also explored how React Developer Tools can be helpful in identifying the specific component to override.

By using component shadowing, you can tailor Volto to your specific needs and preferences, providing greater flexibility and customization options. This technique empowers you to fine-tune the performance of your Volto site and ensure it aligns perfectly with your project requirements.

We hope this blog post has been helpful in guiding you through the process of customizing Volto components using component shadowing.