Skip to content

Instantly share code, notes, and snippets.

@lukaszhanusik
Last active January 25, 2024 21:06
Show Gist options
  • Save lukaszhanusik/96f3510cea3145dffb2b78ef0b7b2a13 to your computer and use it in GitHub Desktop.
Save lukaszhanusik/96f3510cea3145dffb2b78ef0b7b2a13 to your computer and use it in GitHub Desktop.
LWC Popover Docs

Popover Component

Configuration

The list of SLDS components exceeds the LWC component library by a long distance. There are many awesome component blueprints defined in SLDS which are not available in LWC library. This comes in as a challenge during implementation. SLDS only provides the plain HTML without working implementation whereas LWC components are fully functional and can be used readily.

One such component in SLDS is Popover. This does not have a corresponding component in LWC library. As an attempt to create a working SLDS Popover, I have created this utility. The Popover would comprise of three sections i.e. header, body and footer. The component is built on LWC .

To keep the configuration about the component, it takes following parameters:

  • show: Boolean property to control the visibility of the Popover. Default value is false, which means the Popover would be hidden.
  • size: Attribute to control the size of the Popover. Accepted values are small, medium, large and full-width. Default is small.
  • nubbin: Attribute to control the placement of Popover nubbin. Accepted values are left, left-top, left-bottom, top, top-left, top-right, right, right-top, - right-bottom, bottom, bottom-left, bottom-right. Default is left.
  • variant: The Popover does not have any variance by default. But it does allow to show errors and warning. Allowed values are error, warning.
  • header: Plain text to be shown on header. If nothing is specified, header is hidden.
  • body: Plain text to be shown on body. This should be specified, else the component might not render properly.
  • footer: Plain text to be shown on header. If nothing is specified, header is hidden.

The Popover will have a Close button to close the component. As this button is clicked, it dispatches a close event, which can be caught by the parent component and action can be performed accordingly.

Html

<template>
    <div if:true={show}>
        <section class={sectionClass} role="dialog">
            <button class="slds-button slds-button_icon slds-button_icon-small slds-float_right slds-popover__close" title="Close dialog" onclick={closePopover}>
                <lightning-icon icon-name="utility:close" alternative-text="Close" title="Close" size="small" variant={iconVariant}></lightning-icon>
            </button>
            <header class="slds-popover__header" if:true={showHeader}>
                <div class="slds-media slds-media_center slds-has-flexi-truncate">
                    <div class="slds-media__figure" if:true={showIcon}>
                        <span class="slds-icon_container slds-icon-utility-error">
                            <lightning-icon icon-name={iconName} alternative-text="Error" title="Error" size="small" variant={iconVariant}></lightning-icon>
                        </span>
                    </div>
                    <div class="slds-media__body">
                        <h2 class="slds-truncate slds-text-heading_medium">{header}</h2>
                      </div>
                  </div>
            </header>
            <div class="slds-popover__body">
                <p>{body}</p>
            </div>
            <footer class="slds-popover__footer" if:true={showFooter}>
                <p>{footer}</p>
            </footer>
        </section>
    </div>
</template>
import { LightningElement, track, api } from 'lwc';

export default class Popover extends LightningElement {
    @api show;
    @api size;
    @api nubbin;
    @api variant;
    @api header;
    @api body;
    @api footer;

    @track hasRendered = false;

    @track showHeader = false;
    @track showFooter = false;
    @track showIcon = false;
    @track sectionClass;
    @track iconName;
    @track iconVariant;
    
    connectedCallback() {
        if (this.hasRendered == false) {
            this.hasRendered = true;

            this.sectionClass = 'slds-popover';
            this.iconVariant = '';

            //check if header is present
            if (this.header) {
                this.showHeader = true;
            }

            //check if footer is present
            if (this.header) {
                this.showFooter = true;
            }

            //Update classes depending upon Nubin
            this.updateNubin();

            //Udate classes depending upon variant
            this.updateVariant();

            //Udate classes depending upon size
            this.updateSize();
        }
    }

    updateNubin() {
        if (this.nubbin) {
            switch(this.nubbin){
                case 'left' :
                    this.sectionClass += ' slds-nubbin_left';
                    break;
                case 'left-top' :
                    this.sectionClass += ' slds-nubbin_left-top';
                    break;
                case 'left-bottom' :
                    this.sectionClass += ' slds-nubbin_left-bottom';
                    break;
                case 'top' :
                    this.sectionClass += ' slds-nubbin_top';
                    break;
                case 'top-left' :
                    this.sectionClass += ' slds-nubbin_top-left';
                    break;
                case 'top-right' :
                    this.sectionClass += ' slds-nubbin_top-right';
                    break;   
                case 'right' :
                    this.sectionClass += ' slds-nubbin_right';
                    break;
                case 'right-top' :
                    this.sectionClass += ' slds-nubbin_right-top';
                    break;
                case 'right-bottom' :
                    this.sectionClass += ' slds-nubbin_right-bottom';
                    break;
                case 'bottom' :
                    this.sectionClass += ' slds-nubbin_bottom';
                    break;
                case 'bottom-left' :
                    this.sectionClass += ' slds-nubbin_bottom-left';
                    break;
                case 'bottom-right' :
                    this.sectionClass += ' slds-nubbin_bottom-right';
                    break;
                default :
                    this.sectionClass += ' slds-nubbin_left';
            }
        } else {
            this.sectionClass += ' slds-nubbin_left';
        }
    }

    updateVariant() {
        if (this.variant) {
            switch(this.variant){
                case 'error' :
                    this.sectionClass += ' slds-popover_error';
                    this.iconName = 'utility:error';
                    this.showIcon = true;
                    this.iconVariant = 'inverse';
                    break;
                case 'warning' :
                    this.sectionClass += ' slds-popover_warning'
                    this.iconName = 'utility:warning';
                    this.showIcon = true;
                    break;
            }
        }
    }

    updateSize() {
        if (this.size) {
            switch(this.size){
                case 'small' :
                    this.sectionClass += ' slds-popover_small';
                    break;
                case 'medium' :
                    this.sectionClass += ' slds-popover_medium'
                    break;
                case 'large' :
                    this.sectionClass += ' slds-popover_large';
                    break;
                case 'full-width' :
                    this.sectionClass += ' slds-popover_full-width';
                    break;
                default :
                    this.sectionClass += ' slds-popover_small';
            }
        } else {
            this.sectionClass += ' slds-popover_small';
        }
    }

    closePopover() {
        this.show = false;
        this.dispatchEvent(new CustomEvent('close', {}));
    }
}

Sample Component Preview

<template>
    <div class="slds-box slds-theme_shade">
        <div>
            <span class="slds-align_absolute-center">
                <c-popover show={showPopver} onclose={handleClose} body="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." header="Popover Header" footer="This is additional footer." nubbin="bottom-right" size="medium" variant="error" ></c-popover>
            </span>
            <span onmouseover={show}>
                <lightning-progress-indicator current-step="3" type="base" has-error="true" variant="base">
                    <lightning-progress-step label="Step 1" value="1"></lightning-progress-step>
                    <lightning-progress-step label="Step 2" value="2"></lightning-progress-step>
                    <lightning-progress-step label="Step 3" value="3"></lightning-progress-step>
                    <lightning-progress-step label="Step 4" value="4"></lightning-progress-step>
                </lightning-progress-indicator>
            </span>
        </div>
    </div>
</template>
import { LightningElement, track } from 'lwc';

export default class PopoverPreview extends LightningElement {
    @track showPopver = false;
    show() {
        this.showPopver = true;
    }
    hide() {
        this.showPopver = false;
    }
    handleClose() {
        this.showPopver = false;
    }
}
@tomtam-c2fo
Copy link

Hello, I am following your example but the popover ends up pushing the next element downwards instead of floating on top. Any guidance?
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment