Skip to content

Commit

Permalink
feat(esl-share): create popup mode for esl-share component
Browse files Browse the repository at this point in the history
  • Loading branch information
dshovchko committed May 18, 2023
1 parent 7ab9968 commit 0df4116
Showing 1 changed file with 75 additions and 79 deletions.
154 changes: 75 additions & 79 deletions src/modules/esl-share/core/esl-share.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,17 @@
import {ESLBaseElement} from '../../esl-base-element/core';
import {attr, bind, boolAttr, prop} from '../../esl-utils/decorators';

import {ESLPopup} from '../../esl-popup/core';
import {ESLTrigger} from '../../esl-trigger/core';
import {sequentialUID} from '../../esl-utils/misc/uid';
import {attr, boolAttr, prop} from '../../esl-utils/decorators';
import {selectButtonsForList} from './esl-share-config';
import {ESLShareButton} from './esl-share-button';

/** {@link ShareConfig} provider type definition */
export type ESLShareConfigProviderType = () => Promise<ShareConfig>;

/** The definition of the sharing button */
export interface ShareButtonConfig {
/** Name of the share action, which is performed after the button is pressed */
action: string;
/** HTML content of the share icon */
icon: string;
/** Color of the icon background (the value - CSS data type represents a color) */
iconBackground: string;
/**
* URL link (with placeholders) to share. Can contain the following placeholders:
* - `{u}` or `{url}` - URL to share (`shareUrl` property on the {@link ESLShareButton} instance)
* - `{t}` or `{title}` - title to share (`shareTitle` property on the {@link ESLShareButton} instance)
*/
link: string;
/** String identifier of the button (no spaces) */
name: string;
/** Button title */
title: string;
/** Additional params to pass into a button */
additional?: Record<string, any>;
}
import type {ESLShareConfig, ESLShareButtonConfig} from './esl-share-config';

/** The definition of share buttons groups (named sets of share buttons) */
export interface ShareGroupConfig {
/** Name of the group. The group can be accessed with the `group:` prefix in the component configuration */
name: string;
/** A list of button names separated by space */
list: string;
}

/** The definition of `ESLShare` component configuration */
export interface ShareConfig {
/** List of sharing buttons configuration */
buttons: ShareButtonConfig[];
/** List of share button groups configurations */
groups: ShareGroupConfig[];
}

function getConfigSectionItem<T extends ShareButtonConfig | ShareGroupConfig>(section: T[], name: string): T | undefined {
return section.find((item) => item.name === name);
}
/** {@link ShareConfig} provider type definition */
export type ESLShareConfigProviderType = () => Promise<ESLShareConfig>;

function getButtonsList(config: ShareConfig, list: string): ShareButtonConfig[] {
return list.split(' ').reduce((res, item) => {
const [btnName, groupName] = item.split('group:');
if (groupName) {
const groupConfig = getConfigSectionItem(config.groups, groupName);
if (groupConfig) return res.concat(getButtonsList(config, groupConfig.list));
} else {
const btnConfig = getConfigSectionItem(config.buttons, btnName);
if (btnConfig) res.push(btnConfig);
}
return res;
}, [] as ShareButtonConfig[]);
}
const popupStore = new Map<string, ESLPopup>();

/**
* ESLShare
Expand All @@ -70,14 +21,24 @@ function getButtonsList(config: ShareConfig, list: string): ShareButtonConfig[]
*/
export class ESLShare extends ESLBaseElement {
public static override is = 'esl-share';
protected static _config: Promise<ShareConfig> = Promise.reject('Configuration is not set');
protected static _config: Promise<ESLShareConfig> = Promise.reject('Configuration is not set');

/** Register {@link ESLShare} component and dependent {@link ESLShareButton} */
public static override register(): void {
ESLShareButton.register();
super.register();
}

/**
* Gets or updates config with a promise of a new config object or using a config provider function.
* @returns Promise of the current config
*/
public static config(provider?: ESLShareConfigProviderType | ESLShareConfig): Promise<ESLShareConfig> {
if (typeof provider === 'function') ESLShare._config = provider();
if (typeof provider === 'object') ESLShare._config = Promise.resolve(provider);
return ESLShare._config;
}

/** Event to dispatch on ready state of {@link ESLShare} */
@prop('esl:share:ready') public SHARE_READY_EVENT: string;

Expand All @@ -93,25 +54,25 @@ export class ESLShare extends ESLBaseElement {
/** Title to share (current document title by default) */
@attr() public shareTitle: string;
/** Rendering mode of the share buttons ('list' by default) */
@attr() public mode: 'list';
@attr() public mode: 'list' | 'popup';

/** @readonly Ready state marker */
@boolAttr({readonly: true}) public ready: boolean;

/**
* Gets or updates config with a promise of a new config object or using a config provider function.
* @returns Promise of the current config
*/
public static config(provider?: ESLShareConfigProviderType | ShareConfig): Promise<ShareConfig> {
if (typeof provider === 'function') ESLShare._config = provider();
if (typeof provider === 'object') ESLShare._config = Promise.resolve(provider);
return ESLShare._config;
protected _content: string;

protected get $popup(): ESLPopup {
return popupStore.get(this.list) || this.buildPopup$();
}

protected set $popup(value: ESLPopup) {
popupStore.set(this.list, value);
}

/** @returns config of buttons specified by the list attribute */
public get buttonsConfig(): Promise<ShareButtonConfig[]> {
public get buttonsConfig(): Promise<ESLShareButtonConfig[]> {
return (this.constructor as typeof ESLShare).config().then((config) => {
return (this.list !== 'all') ? getButtonsList(config, this.list) : config.buttons;
return (this.list !== 'all') ? selectButtonsForList(config, this.list) : config.buttons;
});
}

Expand All @@ -124,24 +85,45 @@ export class ESLShare extends ESLBaseElement {

protected init(): void {
if (!this.mode) this.mode = 'list';
this.buttonsConfig.then(this.build)
if (!this._content) this._content = this.innerHTML;
this.build()
.then(() => this.$$fire(this.SHARE_READY_EVENT, {bubbles: false}))
.catch((e) => console.error(`[${this.baseTagName}]: ${e}`));
}

/** Builds content of component */
@bind
public build(config: ShareButtonConfig[]): void {
protected async build(): Promise<void> {
this.innerHTML = '';
config.forEach((btnCfg) => {
const btn = this.buildButton(btnCfg);
btn && this.appendChild(btn);

let $buttonsHost!: Element;
if (this.mode === 'popup') {
$buttonsHost = this.$popup;
this.buildTrigger(`#${$buttonsHost.id}`);
}

return this.appendButtons($buttonsHost || this);
}

protected buildTrigger(target: string): void {
const $trigger = ESLTrigger.create();
Object.assign($trigger, {
target,
mode: 'toggle',
trackClick: true,
trackHover: true
});
$trigger.innerHTML = this._content;
this.appendChild($trigger);
}

this.toggleAttribute('ready', true);
protected async appendButtons($el: Element): Promise<void> {
const buttonsConfig = await this.buttonsConfig;
buttonsConfig.forEach((cfg) => {
const btn = this.buildButton(cfg);
btn && $el.appendChild(btn);
});
}

protected buildButton(cfg: ShareButtonConfig): ESLShareButton | null {
protected buildButton(cfg: ESLShareButtonConfig): ESLShareButton | null {
const $button = ESLShareButton.create();
Object.assign($button, cfg);
const $icon = document.createElement('span');
Expand All @@ -152,4 +134,18 @@ export class ESLShare extends ESLBaseElement {
$button.appendChild($icon);
return $button;
}

protected buildPopup$(): ESLPopup {
const id = sequentialUID(this.baseTagName + '-');

const $popup = ESLPopup.create();
Object.assign($popup, {id, position: 'top', defaultParams: {hideDelay: 500}});

const $arrow = document.createElement('span');
$arrow.classList.add('esl-popup-arrow');
$popup.appendChild($arrow); // TODO: consider the feature to append an arrow to popup: $popup.appendArrow()
document.body.appendChild($popup);

return this.$popup = $popup;
}
}

0 comments on commit 0df4116

Please sign in to comment.