mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add overflowTooltip() tool, and use for long tables in widget picker, and long page names.
Summary: Usage is simply to call `overflowTooltip()` with no arguments, as an argument to an element whose text may overflow. On 'mouseenter', it'll check for overflow and show the element's .textContent in a tooltip. - Added for long table names in the widget picker (Add Page, Add Widget to Page). - Added for long page names in the left-panel list of pages. Test Plan: Added test cases for the new overflow tooltips Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3814
This commit is contained in:
parent
b312b3b08b
commit
86681de595
@ -8,6 +8,7 @@ import { linkId, NoLink } from 'app/client/ui/selectBy';
|
||||
import { withInfoTooltip } from 'app/client/ui/tooltips';
|
||||
import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes';
|
||||
import { bigPrimaryButton } from "app/client/ui2018/buttons";
|
||||
import { overflowTooltip } from "app/client/ui/tooltips";
|
||||
import { theme, vars } from "app/client/ui2018/cssVars";
|
||||
import { icon } from "app/client/ui2018/icons";
|
||||
import { spinnerModal } from 'app/client/ui2018/modals';
|
||||
@ -321,7 +322,7 @@ export class PageWidgetSelect extends Disposable {
|
||||
dom.forEach(this._tables, (table) => dom('div',
|
||||
cssEntryWrapper(
|
||||
cssEntry(cssIcon('TypeTable'),
|
||||
cssLabel(dom.text(use => use(table.tableNameDef) || use(table.tableId))),
|
||||
cssLabel(dom.text(table.tableNameDef), overflowTooltip()),
|
||||
dom.on('click', () => this._selectTable(table.id())),
|
||||
cssEntry.cls('-selected', (use) => use(this._value.table) === table.id()),
|
||||
testId('table-label')
|
||||
|
@ -12,6 +12,7 @@ import {menuCssClass} from 'app/client/ui2018/menus';
|
||||
import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs';
|
||||
import Popper from 'popper.js';
|
||||
import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel';
|
||||
import merge = require('lodash/merge');
|
||||
|
||||
export interface ITipOptions {
|
||||
/**
|
||||
@ -25,6 +26,12 @@ export interface ITipOptions {
|
||||
|
||||
/** When set, a tooltip will replace any previous tooltip with the same key. */
|
||||
key?: string;
|
||||
|
||||
/**
|
||||
* Optionally, popper modifiers (e.g. {offset: {offset: 8}}),
|
||||
* See https://popper.js.org/docs/v1/#modifiers.
|
||||
*/
|
||||
modifiers?: Popper.Modifiers;
|
||||
}
|
||||
|
||||
export interface ITransientTipOptions extends ITipOptions {
|
||||
@ -66,6 +73,9 @@ export interface IHoverTipOptions extends ITransientTipOptions {
|
||||
* Should only be set to true if `openOnClick` is false.
|
||||
*/
|
||||
closeOnClick?: boolean;
|
||||
|
||||
/** Whether to show the tooltip only when the ref element overflows horizontally. */
|
||||
overflowOnly?: boolean;
|
||||
}
|
||||
|
||||
export type ITooltipContent = ITooltipContentFunc | DomContents;
|
||||
@ -132,9 +142,13 @@ export function showTooltip(
|
||||
|
||||
// Create a popper for positioning the tooltip content relative to refElem.
|
||||
const popperOptions: Popper.PopperOptions = {
|
||||
modifiers: {preventOverflow: {boundariesElement: 'viewport'}},
|
||||
modifiers: merge(
|
||||
{ preventOverflow: {boundariesElement: 'viewport'} },
|
||||
options.modifiers
|
||||
),
|
||||
placement,
|
||||
};
|
||||
|
||||
const popper = new Popper(refElem, content, popperOptions);
|
||||
|
||||
// If refElem is disposed we close the tooltip.
|
||||
@ -159,6 +173,20 @@ export function hoverTooltip(tipContent: ITooltipContent, options?: IHoverTipOpt
|
||||
return (elem) => setHoverTooltip(elem, tipContent, {...defaultOptions, ...options});
|
||||
}
|
||||
|
||||
/**
|
||||
* On hover, show the full text of this element when it overflows horizontally. It is intended
|
||||
* mainly for styled with "text-overflow: ellipsis".
|
||||
* E.g. dom('label', 'Long text...', overflowTooltip()).
|
||||
*/
|
||||
export function overflowTooltip(options?: IHoverTipOptions): DomElementMethod {
|
||||
const defaultOptions: IHoverTipOptions = {
|
||||
placement: 'bottom-start',
|
||||
overflowOnly: true,
|
||||
modifiers: {offset: {offset: '40, 0'}},
|
||||
};
|
||||
return (elem) => setHoverTooltip(elem, () => elem.textContent, {...defaultOptions, ...options});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a tooltip to the given element, to be rendered on hover.
|
||||
*/
|
||||
@ -167,7 +195,8 @@ export function setHoverTooltip(
|
||||
tipContent: ITooltipContent,
|
||||
options: IHoverTipOptions = {}
|
||||
) {
|
||||
const {key, openDelay = 200, timeoutMs, closeDelay = 100, openOnClick, closeOnClick = true} = options;
|
||||
const {key, openDelay = 200, timeoutMs, closeDelay = 100, openOnClick, closeOnClick = true,
|
||||
overflowOnly = false} = options;
|
||||
|
||||
const tipContentFunc = typeof tipContent === 'function' ? tipContent : () => tipContent;
|
||||
|
||||
@ -204,6 +233,9 @@ export function setHoverTooltip(
|
||||
|
||||
// We simulate hover effect by handling mouseenter/mouseleave.
|
||||
dom.onElem(refElem, 'mouseenter', () => {
|
||||
if (overflowOnly && (refElem as HTMLElement).offsetWidth >= refElem.scrollWidth) {
|
||||
return;
|
||||
}
|
||||
if (!tipControl && !timer) {
|
||||
// If we're replacing a tooltip, open without delay.
|
||||
const delay = key && openTooltips.has(key) ? 0 : openDelay;
|
||||
|
@ -4,7 +4,7 @@ import { cssEditorInput } from "app/client/ui/HomeLeftPane";
|
||||
import { itemHeader, itemHeaderWrapper, treeViewContainer } from "app/client/ui/TreeViewComponentCss";
|
||||
import { theme } from "app/client/ui2018/cssVars";
|
||||
import { icon } from "app/client/ui2018/icons";
|
||||
import { hoverTooltip } from 'app/client/ui/tooltips';
|
||||
import { hoverTooltip, overflowTooltip } from 'app/client/ui/tooltips';
|
||||
import { menu, menuItem, menuText } from "app/client/ui2018/menus";
|
||||
import { dom, domComputed, DomElementArg, makeTestId, observable, Observable, styled } from "grainjs";
|
||||
|
||||
@ -88,6 +88,7 @@ export function buildPageDom(name: Observable<string>, actions: PageActions, ...
|
||||
dom.text(name),
|
||||
testId('label'),
|
||||
dom.on('click', (ev) => isTargetSelected(ev.target as HTMLElement) && isRenaming.set(true)),
|
||||
overflowTooltip(),
|
||||
),
|
||||
cssPageMenuTrigger(
|
||||
cssPageMenuIcon('Dots'),
|
||||
|
@ -259,6 +259,26 @@ describe('Pages', function() {
|
||||
assert.include(await gu.getPageNames(), 'People');
|
||||
});
|
||||
|
||||
it('should show tooltip for long page names on hover', async () => {
|
||||
await gu.openPageMenu('People');
|
||||
await driver.find('.test-docpage-rename').doClick();
|
||||
await driver.find('.test-docpage-editor')
|
||||
.sendKeys('People, Persons, Humans, Ladies & Gentlemen', Key.ENTER);
|
||||
await gu.waitForServer();
|
||||
|
||||
await driver.findContent('.test-treeview-label', /People, Persons, Humans, Ladies & Gentlemen/).mouseMove();
|
||||
await driver.wait(() => driver.findWait('.test-tooltip', 1000).isDisplayed(), 3000);
|
||||
assert.equal(await driver.find('.test-tooltip').getText(),
|
||||
'People, Persons, Humans, Ladies & Gentlemen');
|
||||
|
||||
await gu.undo();
|
||||
assert.deepEqual(await gu.getPageNames(), ['Interactions', 'Documents', 'People', 'User & Leads', 'Overview']);
|
||||
|
||||
await driver.findContent('.test-treeview-label', /People/).mouseMove();
|
||||
await driver.sleep(500);
|
||||
assert.equal(await driver.find('.test-tooltip').isPresent(), false);
|
||||
});
|
||||
|
||||
it('should not change page when clicking the input while renaming page', async () => {
|
||||
// check that initially People is selected
|
||||
assert.match(await driver.find('.test-treeview-itemHeader.selected').getText(), /People/);
|
||||
|
Loading…
Reference in New Issue
Block a user