By Viviana Menzel on Thursday, 19 December 2024
Category: December

Accordion Override for Articles Module

If you have followed my writings in the Magazine, you already know that I really like overrides. Overrides have helped me understand how Joomla works and they give me the possibility to change almost everything without breaking Joomla core. 

Overrides need maintenance like extensions do, but to a small extent, normally are only a few files you need to check after a Joomla update. Of course it is nice to use extensions with ready-to-use options and styles instead of writing your own code, but if you don’t try you don’t learn.

I’m always looking for inspiration for new overrides, new ways to display content on a website. Some weeks ago I stumbled upon a fancy accordion design created by Bramus Van Damme, a web developer from Belgium who is part of the Chrome Developer Relations team at Google, focusing on CSS, Web UI, and DevTools. The accordion is based on the <details> and <summary> elements and that is great, because these elements don’t need any kind of JavaScript and are accessible by default.
I immediately had the idea to create an override for the Articles Module based on this accordion. And that is what we will build:

We need

Override

I converted the HTML code from Bramus’ demo into PHP. The file accordion.php has to be copied into html\mod_articles inside your template structure.

accordion.php

<?php

/**
 * @package     Joomla.Site
 * @subpackage  mod_articles
 *
 * @copyright   (C) 2024 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\CMS\Router\Route;

/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $app->getDocument()->getWebAssetManager();

if (!$list) {
    return;
}

$items = $list;

// Get the category object
$categories = Factory::getApplication()->bootComponent('com_content')->getCategory();

?>

<div class="accordion-wrapper">
    <?php foreach ($items as $item) : ?>
        <?php
            // Get the category id and the fields
            $category   = $categories->get($item->catid);
            $jcfields = FieldsHelper::getFields('com_content.categories', $category, true);

            // Create an associative array for easier access by field name
            foreach($jcfields as $jcfield) {
                $jcfields[$jcfield->name] = $jcfield;
            }

            // Get the intro image of the article
            $images  = json_decode($item->images);
            $layoutAttr = [
                'src' => $images->image_intro,
                'alt' => empty($images->image_intro_alt) ? '' : $images->image_intro_alt,
            ];
        ?>
        <details name="accordion" open >
            <summary aria-label="<?php echo $item->displayCategoryTitle; ?>">
            <span class="<?php echo $jcfields['icon-cat']->value; ?>" aria-hidden="true"></span>
            <?php echo LayoutHelper::render('joomla.html.image', $layoutAttr); ?>
            </summary>
            <div class="details-content-wrapper">
                <?php $item_heading = $params->get('item_heading', 'h4'); ?>
                <<?php echo $item_heading; ?>>
                    <?php echo $item->title; ?>
                </<?php echo $item_heading; ?>>
                <?php echo $item->displayIntrotext; ?>
                <a class="btn btn-accordion" href="<?php echo $item->link; ?>" aria-label="<?php echo Text::sprintf('JGLOBAL_READ_MORE_TITLE', $item->title); ?>">
                <?php echo '<span class="icon-chevron-right" aria-hidden="true"></span>'; ?>
               </a>
            </div>
        </details>
    <?php endforeach; ?>
</div>

We use the category icon in the <summary> element, that is the visible part of the accordion, where one can click to open the details.

The intro image also needs to be inside the <summary>, because otherwise it would be hidden when the <details> element is not open. With CSS we will position the image to look like a background image. We will also use the title and the intro text from each article:

CSS

I added some lines into the CSS from the demo and copied it in the user.css (using Cassiopeia):

.accordion-wrapper {
    display: flex;
    flex-direction: row;
    gap: 1rem;
    width: min-content;
    margin: 0 auto;
}

details {
    display: flex;
    flex-direction: row;
    background: transparent;
    color: white;
    height: 30rem;
    border-radius: 2rem;
    overflow: hidden;
    /* To make the image work …*/
    position: relative;
    z-index: 1;
    /* Hide marker */
    ::marker {
        content: '';
    }
    /* The image is tucked in the summary, because otherwise it would be hidden when not [open] as it ends up in the ::details-content pseudo */
    summary img {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        z-index: -1;
        transition: filter 0.5s ease;
    }
    /* Animate the image */
    &[open] summary img {
        filter: brightness(.3);
    }

    summary {
        padding: 1rem 1em;
        width: 6rem;
        flex-shrink: 0; /* Prevent shrinking */
        text-align: center;

        span {
            display: grid;
            place-content: center;
            width: 100%;
            aspect-ratio: 1;
            border-radius: 50%;
            background: rgb(0, 0, 0, .25);
        }

        &:focus {
            outline: none;
        }
    }

    .details-content-wrapper {
        padding: 1.5rem 1em;
        width: 330px;
    }

    &:hover, &:has(summary:focus-visible) {
        outline: 3px solid var(--cassiopeia-color-primary);
        outline-offset: 3px;
    }
    span::before {
        font-size: 1.5rem;
    }
}

.details-content-wrapper {
    display: flex;
    flex-direction: column;
    /* We need margin-trim … */
    :first-child {
        margin-top: 0;
    }
    :last-child {
        margin-bottom: 0;
    }

    /* Animate-in the text when open */
    p, a {
        transform: translateY(2rem);
        opacity: 0;
        transition: opacity 0.5s ease;
        transition-delay: 0.5s;
    }

    [open] & p,
    [open] & a {
        transform: none;
        opacity: 1;
        transition-delay: 0.5s;
    }

    .btn-accordion {
        background: rgb(0, 0, 0, .25);
        aspect-ratio: 1;
        border-radius: 50%;
        color: currentColor;
        margin-inline-start: auto;
        &:hover, &:focus-visible {
            outline: 3px solid currentColor;
            outline-offset: 2px;
        }
    }
}

The special thing in this override is that it uses “display: flex;” for the <details> element. Sadly it will only work as expected on the last version of Chrome. In other browsers the intro text will display next to the category icon and not side-by-side. Hopefully in the near future other browsers will also implement “display: flex;” (and other display options) for <details>.

Chrome

Firefox

Conclusion

There are lots of examples out there waiting to be converted into Joomla overrides. If you find something inspiring, but you are not able to create an override by yourself, please don’t hesitate to contact me. Maybe you can help me with my next Magazine article 😉

Leave Comments