diff --git a/app/client/components/DetailView.css b/app/client/components/DetailView.css index cf7d026e..4e30d6d9 100644 --- a/app/client/components/DetailView.css +++ b/app/client/components/DetailView.css @@ -244,6 +244,7 @@ .detail_theme_field_form .info_toggle_icon { width: 13px; height: 13px; + margin-bottom: 3px; } /* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label diff --git a/app/client/components/DetailView.js b/app/client/components/DetailView.js index b4d4d753..4b48426a 100644 --- a/app/client/components/DetailView.js +++ b/app/client/components/DetailView.js @@ -15,7 +15,7 @@ const RecordLayout = require('./RecordLayout'); const commands = require('./commands'); const {RowContextMenu} = require('../ui/RowContextMenu'); const {parsePasteForView} = require("./BaseView2"); -const {withInfoTooltip} = require('../ui/tooltips'); +const {columnInfoTooltip} = require("../ui/tooltips"); /** * DetailView component implements a list of record layouts. @@ -228,12 +228,10 @@ DetailView.prototype.buildFieldDom = function(field, row) { if (field.isNewField) { return dom('div.g_record_detail_el.flexitem', kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - field.description.peek() ? - withInfoTooltip( - dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_description', kd.text(field.description)), - ) : dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_value', field.value) + dom('div.g_record_detail_label', + kd.text(field.displayLabel), + kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null) + ), ); } @@ -262,11 +260,10 @@ DetailView.prototype.buildFieldDom = function(field, row) { dom.autoDispose(isCellSelected), dom.autoDispose(isCellActive), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - field.description.peek() ? - withInfoTooltip( - dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_description', kd.text(field.description)), - ) : dom('div.g_record_detail_label', kd.text(field.displayLabel)), + dom('div.g_record_detail_label', + kd.text(field.displayLabel), + kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null) + ), dom('div.g_record_detail_value', kd.toggleClass('scissors', isCopyActive), kd.toggleClass('record-add', row._isAddRow), diff --git a/app/client/components/DocumentUsage.ts b/app/client/components/DocumentUsage.ts index 4dc82616..243c6f64 100644 --- a/app/client/components/DocumentUsage.ts +++ b/app/client/components/DocumentUsage.ts @@ -2,7 +2,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel'; import {urlState} from 'app/client/models/gristUrlState'; import {docListHeader} from 'app/client/ui/DocMenuCss'; import {GristTooltips, TooltipContentFunc} from 'app/client/ui/GristTooltips'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {withInfoTooltip} from 'app/client/ui/tooltips'; import {mediaXSmall, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {loadingDots, loadingSpinner} from 'app/client/ui2018/loaders'; @@ -284,7 +284,7 @@ function buildUsageMetric(options: MetricOptions, ...domArgs: DomElementArg[]) { return cssUsageMetric( cssMetricName( tooltipContentFunc - ? withQuestionMarkTooltip( + ? withInfoTooltip( cssOverflowableText(name, testId('name')), tooltipContentFunc() ) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 03f21b44..d2cf2755 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -5,7 +5,7 @@ import {BEHAVIOR, ColumnRec} from 'app/client/models/entities/ColumnRec'; import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import { withInfoTooltip } from 'app/client/ui/tooltips'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {textButton} from 'app/client/ui2018/buttons'; import { testId, theme, vars } from 'app/client/ui2018/cssVars'; @@ -371,7 +371,7 @@ export function buildFormulaConfig( dom.prop("disabled", disableOtherActions), testId("field-set-formula") )), - cssRow(withQuestionMarkTooltip( + cssRow(withInfoTooltip( textButton( t("Set trigger formula"), dom.on("click", setTrigger), @@ -424,7 +424,7 @@ export function buildFormulaConfig( // Else offer a way to convert to trigger formula. dom.maybe((use) => !(use(maybeTrigger) || use(origColumn.hasTriggerFormula)), () => [ cssEmptySeparator(), - cssRow(withQuestionMarkTooltip( + cssRow(withInfoTooltip( textButton( t("Set trigger formula"), dom.on("click", convertDataColumnToTriggerColumn), diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts index 6d351fa6..8f4b278f 100644 --- a/app/client/ui/PageWidgetPicker.ts +++ b/app/client/ui/PageWidgetPicker.ts @@ -5,7 +5,7 @@ import { reportError } from 'app/client/models/AppModel'; import { ColumnRec, TableRec, ViewSectionRec } from 'app/client/models/DocModel'; import { GristTooltips } from 'app/client/ui/GristTooltips'; import { linkId, NoLink } from 'app/client/ui/selectBy'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import { withInfoTooltip } from 'app/client/ui/tooltips'; import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes'; import { bigPrimaryButton } from "app/client/ui2018/buttons"; import { theme, vars } from "app/client/ui2018/cssVars"; @@ -358,7 +358,7 @@ export class PageWidgetSelect extends Disposable { cssFooterContent( // If _selectByOptions exists and has more than then "NoLinkOption", show the selector. dom.maybe((use) => this._selectByOptions && use(this._selectByOptions).length > 1, () => - withQuestionMarkTooltip( + withInfoTooltip( cssSelectBy( cssSmallLabel('SELECT BY'), dom.update(cssSelect(this._value.link, this._selectByOptions!), diff --git a/app/client/ui/ShareMenu.ts b/app/client/ui/ShareMenu.ts index 572e45e7..1c65bd9f 100644 --- a/app/client/ui/ShareMenu.ts +++ b/app/client/ui/ShareMenu.ts @@ -5,7 +5,7 @@ import {docUrl, urlState} from 'app/client/models/gristUrlState'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu'; import {sendToDrive} from 'app/client/ui/sendToDrive'; -import { hoverTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {hoverTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; import {primaryButton} from 'app/client/ui2018/buttons'; import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars'; @@ -207,7 +207,7 @@ function menuWorkOnCopy(pageModel: DocPageModel) { return [ menuItem(makeUnsavedCopy, t("Work on a Copy"), testId('work-on-copy')), menuText( - withQuestionMarkTooltip( + withInfoTooltip( t("Edit without affecting the original"), GristTooltips.workOnACopy(), {tooltipMenuOptions: {attach: null}} diff --git a/app/client/ui/UserManager.ts b/app/client/ui/UserManager.ts index e8bffb4a..5b12fb1f 100644 --- a/app/client/ui/UserManager.ts +++ b/app/client/ui/UserManager.ts @@ -28,7 +28,7 @@ import {UserManagerModel, UserManagerModelImpl} from 'app/client/models/UserMana import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {shadowScroll} from 'app/client/ui/shadowScroll'; -import { hoverTooltip, ITooltipControl, showTransientTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {hoverTooltip, ITooltipControl, showTransientTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; import {createUserImage} from 'app/client/ui/UserImage'; import {cssMemberBtn, cssMemberImage, cssMemberListItem, cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem, @@ -169,7 +169,7 @@ function buildUserManagerModal( testId('um-cancel') ), (model.resourceType === 'document' && model.gristDoc && !model.isPersonal - ? withQuestionMarkTooltip( + ? withInfoTooltip( cssLink({href: urlState().makeUrl({docPage: 'acl'})}, dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''), 'Open Access Rules', diff --git a/app/client/ui/tooltips.ts b/app/client/ui/tooltips.ts index 73866d42..12ca62c8 100644 --- a/app/client/ui/tooltips.ts +++ b/app/client/ui/tooltips.ts @@ -9,7 +9,7 @@ import {prepareForTransition} from 'app/client/ui/transitions'; import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menuCssClass} from 'app/client/ui2018/menus'; -import { dom, DomContents, DomElementArg, DomElementMethod, IDomArgs, styled } from 'grainjs'; +import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs'; import Popper from 'popper.js'; import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel'; @@ -240,59 +240,43 @@ export function tooltipCloseButton(ctl: ITooltipControl): HTMLElement { } /** - * Renders an icon that shows a tooltip with the specified `content` on click. + * Renders an info icon that shows a tooltip with the specified `content` on click. */ -function iconTooltip( - cssStyledFunc: (...args: IDomArgs) => HTMLElement, - tooltipButtonContent: HTMLElement, - content: DomContents, - menuOptions?: IMenuOptions, - ...domArgs: DomElementArg[] -) { - return cssStyledFunc(tooltipButtonContent, (elem) => { - setPopupToCreateDom( - elem, - (ctl) => { - return cssInfoTooltipPopup( - cssInfoTooltipPopupCloseButton( - icon("CrossSmall"), - dom.on("click", () => ctl.close()), - testId("info-tooltip-close") - ), - cssInfoTooltipPopupBody(content, testId("info-tooltip-popup-body")), - dom.cls(menuCssClass), - dom.cls(cssMenu.className), - dom.onKeyDown({ - Enter: () => ctl.close(), - Escape: () => ctl.close(), - }), - (popup) => { - setTimeout(() => popup.focus(), 0); - }, - testId("info-tooltip-popup") - ); - }, - { ...defaultMenuOptions, ...{ placement: "bottom-end" }, ...menuOptions } - ); - }); +export function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { + return cssInfoTooltipButton('?', + (elem) => { + setPopupToCreateDom( + elem, + (ctl) => { + return cssInfoTooltipPopup( + cssInfoTooltipPopupCloseButton( + icon('CrossSmall'), + dom.on('click', () => ctl.close()), + testId('info-tooltip-close'), + ), + cssInfoTooltipPopupBody( + content, + testId('info-tooltip-popup-body'), + ), + dom.cls(menuCssClass), + dom.cls(cssMenu.className), + dom.onKeyDown({ + Enter: () => ctl.close(), + Escape: () => ctl.close(), + }), + (popup) => { setTimeout(() => popup.focus(), 0); }, + testId('info-tooltip-popup'), + ); + }, + {...defaultMenuOptions, ...{placement: 'bottom-end'}, ...menuOptions}, + ); + }, + testId('info-tooltip'), + ...domArgs, + ); } -function questionMarkTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { - return iconTooltip( - cssQuestionMarkTooltipButton, - cssQuestionMark('?', testId('info-tooltip'), ...domArgs), - content, - menuOptions); -} - -function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { - return iconTooltip( - cssInfoTooltipButton, - icon('Info', dom.cls('info_toggle_icon'), testId('info-tooltip'), ...domArgs), - content, menuOptions); -} - -export interface WithIconTooltipOptions { +export interface WithInfoTooltipOptions { domArgs?: DomElementArg[]; tooltipButtonDomArgs?: DomElementArg[]; tooltipMenuOptions?: IMenuOptions; @@ -312,30 +296,17 @@ export interface WithIconTooltipOptions { * * Usage: * - * withQuestionMarkTooltip( + * withInfoTooltip( * dom('div', 'Hello World!'), * dom('p', 'This is some text to show inside the tooltip.'), * ) */ -export function withQuestionMarkTooltip( - domContents: DomContents, - tooltipContent: DomContents, - options: WithIconTooltipOptions = {}, -) { - const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options; - return cssDomWithTooltip( - domContents, - questionMarkTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), - ...(domArgs ?? []) - ); -} - export function withInfoTooltip( domContents: DomContents, tooltipContent: DomContents, - options: WithIconTooltipOptions = {}, + options: WithInfoTooltipOptions = {}, ) { - const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options; + const {domArgs, tooltipButtonDomArgs, tooltipMenuOptions} = options; return cssDomWithTooltip( domContents, infoTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), @@ -343,6 +314,59 @@ export function withInfoTooltip( ); } +/** + * Renders an column info icon that shows a tooltip with the specified `content` on click. + */ + export function columnInfoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { + return cssColumnInfoTooltipButton( + icon('Info', dom.cls("info_toggle_icon")), + (elem) => { + setPopupToCreateDom( + elem, + (ctl) => { + return cssInfoTooltipPopup( + cssInfoTooltipPopupCloseButton( + icon('CrossSmall'), + dom.on('click', () => ctl.close()), + testId('column-info-tooltip-close'), + ), + cssInfoTooltipPopupBody( + content, + testId('column-info-tooltip-popup-body'), + ), + dom.cls(menuCssClass), + dom.cls(cssMenu.className), + dom.onKeyDown({ + Enter: () => ctl.close(), + Escape: () => ctl.close(), + }), + (popup) => { setTimeout(() => popup.focus(), 0); }, + testId('column-info-tooltip-popup'), + ); + }, + { ...defaultMenuOptions, ...{ placement: 'bottom-end' }, ...menuOptions }, + ); + }, + testId('column-info-tooltip'), + ...domArgs, + ); +} + +const cssColumnInfoTooltipButton = styled('div', ` + cursor: pointer; + --icon-color: ${theme.infoButtonFg}; + border-radius: 50%; + display: inline-block; + margin-left: 5px; + &:hover { + --icon-color: ${theme.infoButtonHoverFg}; + } + &:active { + --icon-color: ${theme.infoButtonActiveFg}; + } +`); + + const cssTooltip = styled('div', ` position: absolute; z-index: 5000; /* should be higher than a modal */ @@ -376,7 +400,7 @@ const cssTooltipCloseButton = styled('div', ` } `); -const cssQuestionMark = styled('div', ` +const cssInfoTooltipButton = styled('div', ` flex-shrink: 0; display: flex; align-items: center; @@ -395,25 +419,6 @@ const cssQuestionMark = styled('div', ` } `); -const cssQuestionMarkTooltipButton = styled('div', ` - cursor: pointer; -`); - -const cssInfoTooltipButton = styled('div', ` - cursor: pointer; - --icon-color: ${theme.infoButtonFg}; - border-radius: 50%; - &:hover { - --icon-color: ${theme.infoButtonHoverFg}; - } - &:active { - --icon-color: ${theme.infoButtonActiveFg}; - } - & > .info_toggle_icon { - display: block; /* don't create a line */ - } -`); - const cssInfoTooltipPopup = styled('div', ` display: flex; flex-direction: column; diff --git a/app/client/widgets/ConditionalStyle.ts b/app/client/widgets/ConditionalStyle.ts index 945b07d2..7ae0f87c 100644 --- a/app/client/widgets/ConditionalStyle.ts +++ b/app/client/widgets/ConditionalStyle.ts @@ -6,7 +6,7 @@ import {RuleOwner} from 'app/client/models/RuleOwner'; import {Style} from 'app/client/models/Styles'; import {cssFieldFormula} from 'app/client/ui/FieldConfig'; import {GristTooltips} from 'app/client/ui/GristTooltips'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {withInfoTooltip} from 'app/client/ui/tooltips'; import {textButton} from 'app/client/ui2018/buttons'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; import {theme, vars} from 'app/client/ui2018/cssVars'; @@ -71,7 +71,7 @@ export class ConditionalStyle extends Disposable { return [ cssRow( { style: 'margin-top: 16px' }, - withQuestionMarkTooltip( + withInfoTooltip( textButton( t('Add conditional style'), testId('add-conditional-style'),