Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(popover): Add hint support #2711

Merged
merged 21 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
- `Improvement` - The API `blocks.convert()` now returns the new block API
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
- `New` – *Menu Config* – New item type – HTML
– `Refactoring` – Switched to Vite as Cypress bundler
– `New` – *Menu Config* – Default and HTML items now support hints

### 2.29.1

Expand Down
16 changes: 16 additions & 0 deletions src/components/utils/popover/components/hint/hint.const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { bem } from '../../../bem';

/**
* Hint block CSS class constructor
*/
const className = bem('ce-hint');

/**
* CSS class names to be used in hint class
*/
export const css = {
root: className(),
alignedLeft: className(null, 'align-left'),
title: className('title'),
description: className('description'),
};
10 changes: 10 additions & 0 deletions src/components/utils/popover/components/hint/hint.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.ce-hint {
&--align-left {
text-align: left;
}

&__description {
opacity: 0.6;
margin-top: 3px;
}
}
46 changes: 46 additions & 0 deletions src/components/utils/popover/components/hint/hint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Dom from '../../../../dom';
import { css } from './hint.const';
import { HintParams } from './hint.types';

import './hint.css';

/**
* Represents the hint content component
*/
export class Hint {
/**
* Html element used to display hint content on screen
*/
private nodes: {
root: HTMLElement;
title: HTMLElement;
description?: HTMLElement;
};

/**
* Constructs the hint content instance
*
* @param params - hint content parameters
*/
constructor(params: HintParams) {
this.nodes = {
root: Dom.make('div', [css.root, css.alignedLeft]),
title: Dom.make('div', css.title, { textContent: params.title }),
};

this.nodes.root.appendChild(this.nodes.title);

if (params.description !== undefined) {
this.nodes.description = Dom.make('div', css.description, { textContent: params.description });

this.nodes.root.appendChild(this.nodes.description);
}
}

/**
* Returns the root element of the hint content
*/
public getElement(): HTMLElement {
return this.nodes.root;
}
}
19 changes: 19 additions & 0 deletions src/components/utils/popover/components/hint/hint.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Hint parameters
*/
export interface HintParams {
/**
* Title of the hint
*/
title: string;

/**
* Secondary text to be displayed below the title
*/
description?: string;
}

/**
* Possible hint positions
*/
export type HintPosition = 'top' | 'bottom' | 'left' | 'right';
2 changes: 2 additions & 0 deletions src/components/utils/popover/components/hint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './hint';
export * from './hint.types';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import Dom from '../../../../../dom';
import { IconDotCircle, IconChevronRight } from '@codexteam/icons';
import {
PopoverItemDefaultParams as PopoverItemDefaultParams,
PopoverItemParams as PopoverItemParams
PopoverItemParams as PopoverItemParams,
PopoverItemRenderParamsMap,
PopoverItemType
} from '../popover-item.types';
import { PopoverItem } from '../popover-item';
import { css } from './popover-item-default.const';
Expand All @@ -11,8 +13,9 @@ import { css } from './popover-item-default.const';
* Represents sigle popover item node
*
* @todo move nodes initialization to constructor
* @todo replace multiple make() usages with constructing separate instaces
* @todo replace multiple make() usages with constructing separate instances
* @todo split regular popover item and popover item with confirmation to separate classes
* @todo display icon on the right side of the item for rtl languages
*/
export class PopoverItemDefault extends PopoverItem {
/**
Expand Down Expand Up @@ -72,10 +75,6 @@ export class PopoverItemDefault extends PopoverItem {
icon: null,
};

/**
* Popover item params
*/
private params: PopoverItemDefaultParams;

/**
* If item is in confirmation state, stores confirmation params such as icon, label, onActivate callback and so on
Expand All @@ -86,12 +85,13 @@ export class PopoverItemDefault extends PopoverItem {
* Constructs popover item instance
*
* @param params - popover item construction params
* @param renderParams - popover item render params.
* The parameters that are not set by user via popover api but rather depend on technical implementation
*/
constructor(params: PopoverItemDefaultParams) {
constructor(private readonly params: PopoverItemDefaultParams, renderParams?: PopoverItemRenderParamsMap[PopoverItemType.Default]) {
super();

this.params = params;
this.nodes.root = this.make(params);
this.nodes.root = this.make(params, renderParams);
}

/**
Expand Down Expand Up @@ -159,8 +159,9 @@ export class PopoverItemDefault extends PopoverItem {
* Constructs HTML element corresponding to popover item params
*
* @param params - item construction params
* @param renderParams - popover item render params
*/
private make(params: PopoverItemDefaultParams): HTMLElement {
private make(params: PopoverItemDefaultParams, renderParams?: PopoverItemRenderParamsMap[PopoverItemType.Default]): HTMLElement {
const el = Dom.make('div', css.container);

if (params.name) {
Expand Down Expand Up @@ -197,6 +198,13 @@ export class PopoverItemDefault extends PopoverItem {
el.classList.add(css.disabled);
}

if (params.hint !== undefined && renderParams?.hint?.enabled !== false) {
this.addHint(el, {
...params.hint,
position: renderParams?.hint?.position || 'right',
});
}

return el;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PopoverItem } from '../popover-item';
import { PopoverItemHtmlParams } from '../popover-item.types';
import { PopoverItemHtmlParams, PopoverItemRenderParamsMap, PopoverItemType } from '../popover-item.types';
import { css } from './popover-item-html.const';
import Dom from '../../../../../dom';

Expand All @@ -16,15 +16,24 @@ export class PopoverItemHtml extends PopoverItem {
* Constructs the instance
*
* @param params – instance parameters
* @param renderParams – popover item render params.
* The parameters that are not set by user via popover api but rather depend on technical implementation
*/
constructor(params: PopoverItemHtmlParams) {
constructor(params: PopoverItemHtmlParams, renderParams?: PopoverItemRenderParamsMap[PopoverItemType.Html]) {
super();

this.nodes = {
root: Dom.make('div', css.root),
};

this.nodes.root.appendChild(params.element);

if (params.hint !== undefined && renderParams?.hint?.enabled !== false) {
this.addHint(this.nodes.root, {
...params.hint,
position: renderParams?.hint?.position || 'right',
});
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import * as tooltip from '../../../../utils/tooltip';
import { type HintPosition, Hint } from '../hint';

/**
* Popover item abstract class
*/
export abstract class PopoverItem {
/**
* Adds hint to the item element if hint data is provided
*
* @param itemElement - popover item root element to add hint to
* @param hintData - hint data
*/
protected addHint(itemElement: HTMLElement, hintData: { title: string, description?: string; position: HintPosition }): void {
const content = new Hint(hintData);

tooltip.onHover(itemElement, content.getElement(), {
placement: hintData.position,
hidingDelay: 100,
});
}

/**
* Returns popover item root element
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { HintParams, HintPosition } from '../hint';

/**
* Popover item types
*/
Expand Down Expand Up @@ -35,7 +37,12 @@ export interface PopoverItemHtmlParams {
/**
* Custom html content to be displayed in the popover
*/
element: HTMLElement
element: HTMLElement;

/**
* Hint data to be displayed on item hover
*/
hint?: HintParams;
}

/**
Expand Down Expand Up @@ -89,6 +96,11 @@ interface PopoverItemDefaultBaseParams {
* In case of string, works like radio buttons group and highlights as inactive any other item that has same toggle key value.
*/
toggle?: boolean | string;

/**
* Hint data to be displayed on item hover
*/
hint?: HintParams;
}

/**
Expand Down Expand Up @@ -117,7 +129,6 @@ export interface PopoverItemWithoutConfirmationParams extends PopoverItemDefault
* @param event - event that initiated item activation
*/
onActivate: (item: PopoverItemParams, event?: PointerEvent) => void;

}


Expand Down Expand Up @@ -152,3 +163,28 @@ export type PopoverItemParams =
PopoverItemSeparatorParams |
PopoverItemHtmlParams;


/**
* Popover item render params.
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
* The parameters that are not set by user via popover api but rather depend on technical implementation
*/
export type PopoverItemRenderParamsMap = {
[key in PopoverItemType.Default | PopoverItemType.Html]?: {
/**
* Hint render params
*/
hint?: {
/**
* Hint position relative to the item
*/
position?: HintPosition;

/**
* If false, hint will not be rendered.
* True by default.
* Used to disable hints on mobile popover
*/
enabled: boolean;
}
};
};
14 changes: 10 additions & 4 deletions src/components/utils/popover/popover-abstract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PopoverItem, PopoverItemDefault, PopoverItemSeparator, PopoverItemType } from './components/popover-item';
import { PopoverItem, PopoverItemDefault, PopoverItemRenderParamsMap, PopoverItemSeparator, PopoverItemType } from './components/popover-item';
import Dom from '../../dom';
import { SearchInput, SearchInputEvent, SearchableItem } from './components/search-input';
import EventsDispatcher from '../events';
Expand Down Expand Up @@ -39,6 +39,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
*/
protected search: SearchInput | undefined;


/**
* Messages that will be displayed in popover
*/
Expand All @@ -51,8 +52,13 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
* Constructs the instance
*
* @param params - popover construction params
* @param itemsRenderParams - popover item render params.
* The parameters that are not set by user via popover api but rather depend on technical implementation
*/
constructor(protected readonly params: PopoverParams) {
constructor(
protected readonly params: PopoverParams,
protected readonly itemsRenderParams: PopoverItemRenderParamsMap = {}
) {
super();

this.items = this.buildItems(params.items);
Expand Down Expand Up @@ -154,9 +160,9 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
case PopoverItemType.Separator:
return new PopoverItemSeparator();
case PopoverItemType.Html:
return new PopoverItemHtml(item);
return new PopoverItemHtml(item, this.itemsRenderParams[PopoverItemType.Html]);
default:
return new PopoverItemDefault(item);
return new PopoverItemDefault(item, this.itemsRenderParams[PopoverItemType.Default]);
}
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/utils/popover/popover-desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { PopoverItemHtml } from './components/popover-item/popover-item-html/pop
/**
* Desktop popover.
* On desktop devices popover behaves like a floating element. Nested popover appears at right or left side.
*
* @todo support rtl for nested popovers and search
*/
export class PopoverDesktop extends PopoverAbstract {
/**
Expand Down
13 changes: 10 additions & 3 deletions src/components/utils/popover/popover-mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import ScrollLocker from '../scroll-locker';
import { PopoverHeader } from './components/popover-header';
import { PopoverStatesHistory } from './utils/popover-states-history';
import { PopoverMobileNodes, PopoverParams } from './popover.types';
import { PopoverItemDefault, PopoverItemParams } from './components/popover-item';
import { PopoverItemDefault, PopoverItemParams, PopoverItemType } from './components/popover-item';
import { css } from './popover.const';
import Dom from '../../dom';


/**
* Mobile Popover.
* On mobile devices Popover behaves like a fixed panel at the bottom of screen. Nested item appears like "pages" with the "back" button
Expand Down Expand Up @@ -41,7 +42,13 @@ export class PopoverMobile extends PopoverAbstract<PopoverMobileNodes> {
* @param params - popover params
*/
constructor(params: PopoverParams) {
super(params);
super(params, {
[PopoverItemType.Default]: {
hint: {
enabled: false,
},
},
});

this.nodes.overlay = Dom.make('div', [css.overlay, css.overlayHidden]);
this.nodes.popover.insertBefore(this.nodes.overlay, this.nodes.popover.firstChild);
Expand Down Expand Up @@ -112,8 +119,8 @@ export class PopoverMobile extends PopoverAbstract<PopoverMobileNodes> {
/**
* Removes rendered popover items and header and displays new ones
*
* @param title - new popover header text
* @param items - new popover items
* @param title - new popover header text
*/
private updateItemsAndHeader(items: PopoverItemParams[], title?: string ): void {
/** Re-render header */
Expand Down