mirror of
https://github.com/codex-team/editor.js
synced 2024-06-01 13:32:43 +02:00
feat(popover): Add hint support (#2711)
* Add custom item * Remove customcontent parameter from popover * Tests * Cleanup * Cleanup * Lint * Cleanup * Rename custom to html, add enum with item types * Fix tests * Support hint * Rename hint content to hint * Align hint left * Move types and exports * Update changelog * Cleanup * Add todos * Change the way hint is disabled for mobile * Get rid of buildItems override * Update comment
This commit is contained in:
parent
50f43bb35d
commit
d18eeb5dc8
|
@ -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
|
||||
|
||||
|
|
16
src/components/utils/popover/components/hint/hint.const.ts
Normal file
16
src/components/utils/popover/components/hint/hint.const.ts
Normal file
|
@ -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
src/components/utils/popover/components/hint/hint.css
Normal file
10
src/components/utils/popover/components/hint/hint.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.ce-hint {
|
||||
&--align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&__description {
|
||||
opacity: 0.6;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
46
src/components/utils/popover/components/hint/hint.ts
Normal file
46
src/components/utils/popover/components/hint/hint.ts
Normal file
|
@ -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
src/components/utils/popover/components/hint/hint.types.ts
Normal file
19
src/components/utils/popover/components/hint/hint.types.ts
Normal file
|
@ -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
src/components/utils/popover/components/hint/index.ts
Normal file
2
src/components/utils/popover/components/hint/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './hint';
|
||||
export * from './hint.types';
|
|
@ -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';
|
||||
|
@ -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 {
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -16,8 +16,10 @@ 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 = {
|
||||
|
@ -25,6 +27,13 @@ export class PopoverItemHtml extends PopoverItem {
|
|||
};
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { HintParams, HintPosition } from '../hint';
|
||||
|
||||
/**
|
||||
* Popover item types
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +129,6 @@ export interface PopoverItemWithoutConfirmationParams extends PopoverItemDefault
|
|||
* @param event - event that initiated item activation
|
||||
*/
|
||||
onActivate: (item: PopoverItemParams, event?: PointerEvent) => void;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -152,3 +163,28 @@ export type PopoverItemParams =
|
|||
PopoverItemSeparatorParams |
|
||||
PopoverItemHtmlParams;
|
||||
|
||||
|
||||
/**
|
||||
* Popover item render params.
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
@ -39,6 +39,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|||
*/
|
||||
protected search: SearchInput | undefined;
|
||||
|
||||
|
||||
/**
|
||||
* Messages that will be displayed in popover
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue