PnP React Controls Part 3 – Accessible Accordion Control

Introduction

Hi friends, this is part 3 where I will explain the usage of the Accessible Accordion control. We will see how we can leverage this control on our SPFx projects and make use of the feature provided by this control. Thanks to the community.

About the Control

This control allows us to render the content in an accordion. This is a react based control and has few customizable properties that can benefit us in customizing the display and the way the control works to the end user.

Focus on the Code

You can refer Part 1 post on the creation of the SPFx project and installing all the dependencies. In addition to the dependencies mentioned in that post you have to install @pnp/sp – v3.5.1. You should also do some changes to the project files to make the pnpjs version 3 to work on SPFx 1.14.0. Please follow this link to do the changes.

Let us see the file webpart.ts

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
    IPropertyPaneConfiguration,
    PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import { SPFI, spfi, SPFx } from "@pnp/sp";
import * as strings from 'AccessibleAccordionDemoWebPartStrings';
import AccessibleAccordionDemo from './components/AccessibleAccordionDemo';
import { IAccessibleAccordionDemoProps } from './components/IAccessibleAccordionDemoProps';

export interface IAccessibleAccordionDemoWebPartProps {
    description: string;
}

export default class AccessibleAccordionDemoWebPart extends BaseClientSideWebPart<IAccessibleAccordionDemoWebPartProps> {

    private _isDarkTheme: boolean = false;
    private _environmentMessage: string = '';
    private sp: SPFI = null;
    protected themeVariant: IReadonlyTheme;

    protected onInit(): Promise<void> {
        this._environmentMessage = this._getEnvironmentMessage();
        this.sp = spfi().using(SPFx(this.context));
        return super.onInit();
    }

    public render(): void {
        const element: React.ReactElement<IAccessibleAccordionDemoProps> = React.createElement(
            AccessibleAccordionDemo,
            {
                description: this.properties.description,
                isDarkTheme: this._isDarkTheme,
                environmentMessage: this._environmentMessage,
                hasTeamsContext: !!this.context.sdks.microsoftTeams,
                userDisplayName: this.context.pageContext.user.displayName,
                sp: this.sp,
                theme: this.themeVariant
            }
        );

        ReactDom.render(element, this.domElement);
    }

    private _getEnvironmentMessage(): string {
        if (!!this.context.sdks.microsoftTeams) { // running in Teams
            return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
        }

        return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment;
    }

    protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
        if (!currentTheme) {
            return;
        }
        this.themeVariant = currentTheme;
        this._isDarkTheme = !!currentTheme.isInverted;
        const {
            semanticColors
        } = currentTheme;
        this.domElement.style.setProperty('--bodyText', semanticColors.bodyText);
        this.domElement.style.setProperty('--link', semanticColors.link);
        this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered);

    }

    protected onDispose(): void {
        ReactDom.unmountComponentAtNode(this.domElement);
    }

    protected get dataVersion(): Version {
        return Version.parse('1.0');
    }

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
            pages: [
                {
                    header: {
                        description: strings.PropertyPaneDescription
                    },
                    groups: [
                        {
                            groupName: strings.BasicGroupName,
                            groupFields: [
                                PropertyPaneTextField('description', {
                                    label: strings.DescriptionFieldLabel
                                })
                            ]
                        }
                    ]
                }
            ]
        };
    }
}

Below are the changes that are done to the default file

  • Created a private variable for themeVariant so that we can pass it to the component and use it in the control. Since the props has to be passed to the component, we need to create a new props variable in the webpart props file.
  • Since we are displaying the items from the list, and to fetch the items from the list we have used pnpjs. So we have to setup the sp context using the factory method.

Let us see the file webpart.tsx

import * as React from 'react';
import { useEffect, useState } from 'react';
import styles from './AccessibleAccordionDemo.module.scss';
import { IAccessibleAccordionDemoProps } from './IAccessibleAccordionDemoProps';
import {
    Accordion,
    AccordionItem,
    AccordionItemHeading,
    AccordionItemButton,
    AccordionItemPanel,
} from "@pnp/spfx-controls-react/lib/AccessibleAccordion";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";

const AccessibleAccordionDemo: React.FC<IAccessibleAccordionDemoProps> = (props) => {
    const [loading, setLoading] = useState<boolean>(true);
    const [items, setItems] = useState<any[]>(undefined);

    const getSampleItems = async (): Promise<void> => {
        const items: any[] = await props.sp.web.lists.getByTitle('Announcements')
            .select('Title', 'Description')
            .items();
        setItems(items);
        setLoading(false);
    };

    useEffect(() => {
        getSampleItems();
    }, []);

    return (
        <section className={`${styles.accessibleAccordionDemo} ${props.hasTeamsContext ? styles.teams : ''}`}>
            <div>
                {loading ? (
                    <div>loading...</div>
                ) : (
                    <>
                        {items && items.length > 0 ? (
                            <Accordion theme={props.theme}>
                                {items.map(item => {
                                    return (
                                        <AccordionItem className={styles.accitem}>
                                            <AccordionItemHeading className={styles.accheading}>
                                                <AccordionItemButton>
                                                    {item.Title}
                                                </AccordionItemButton>
                                            </AccordionItemHeading>
                                            <AccordionItemPanel>
                                                <p>
                                                    {item.Description}
                                                </p>
                                            </AccordionItemPanel>
                                        </AccordionItem >
                                    )
                                })}
                            </Accordion >
                        ) : (
                            <div>Sorry, no items found.</div>
                        )}
                    </>
                )}
            </div >
        </section >
    );
};

export default AccessibleAccordionDemo;
  • The above code use React hooks instead of class component for better understanding
  • Add the required imports for AccessibleAccordion and PnPJs.
  • Created 2 state variable.
    • To display loading text untill fetching the items from the list
    • Store the items fetched from the list.
  • By default, displaying the loading text and on the component mount, fetching the items from the list and store it in the variable and also hide the loading text
  • Based on the items variable, loop through the items and add the Accordion Item control for each item.

Following are some of the key properties provided by the AccessibleAccordion control

  • theme – The OOB theme or custom theme that control should use
  • allowMultipleExpanded – Don’t autocollapse items when expanding other items
  • allowZeroExpanded – Allow the only remaining item to be expanded
  • preExpanded – Array of items to be expanded on loading
  • className – Custom styles to be applied
  • onChange – Event triggered when collapsed or expanded

Reference URL

AccessibleAccordion – @pnp/spfx-controls-react

Sample Implementation

Conclussion

I hope you had learned something about one of the pnp control. There are lot to come in future.

Please free to post your comments and let me know if you want me to post article on any particular feature or plugins that relates to SharePoint. Thanks for your support.

sudharsank/pnpcontrols-demo: Demo project on different PnP React Controls. (github.com)

Happy Coding…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s