feat(ColumnDesc): Info Tooltip with column desc in DetailView

This commit is contained in:
Camille 2023-01-19 18:37:24 +01:00
parent 969280629b
commit f0dc24306a
10 changed files with 120 additions and 77 deletions

View File

@ -37,13 +37,6 @@
font-weight: bold; font-weight: bold;
} }
.g_record_detail_description {
min-height: 1rem;
color: #666;
font-size: 0.9rem;
font-weight: normal;
margin-bottom: 2px;
}
.g_record_detail_value { .g_record_detail_value {
position: relative; position: relative;
min-height: 16px; min-height: 16px;
@ -237,7 +230,7 @@
padding: 1px 1px 1px 5px; padding: 1px 1px 1px 5px;
} }
.detail_theme_field_form > .g_record_detail_label { .detail_theme_field_form>.g_record_detail_label {
font-size: var(--grist-small-font-size); font-size: var(--grist-small-font-size);
color: var(--grist-theme-card-form-label, var(--grist-color-slate)); color: var(--grist-theme-card-form-label, var(--grist-color-slate));
font-weight: bold; font-weight: bold;
@ -248,16 +241,11 @@
margin-right: -8px; margin-right: -8px;
} }
.detail_theme_field_form>.g_record_detail_description { .detail_theme_field_form .info_toggle_icon {
font-size: var(--grist-small-font-size); width: 13px;
color: var(--grist-theme-card-form-label, var(--grist-color-slate)); height: 13px;
font-weight: normal;
min-height: 0px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: -8px;
} }
/* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label /* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label
* inside value box for compact view for better cursor looks, etc), but first the cell editor * inside value box for compact view for better cursor looks, etc), but first the cell editor
* needs to learn to match the value box's style. Right now, the cell editor style is hard-coded. * needs to learn to match the value box's style. Right now, the cell editor style is hard-coded.

View File

@ -15,6 +15,7 @@ const RecordLayout = require('./RecordLayout');
const commands = require('./commands'); const commands = require('./commands');
const {RowContextMenu} = require('../ui/RowContextMenu'); const {RowContextMenu} = require('../ui/RowContextMenu');
const {parsePasteForView} = require("./BaseView2"); const {parsePasteForView} = require("./BaseView2");
const {withInfoTooltip} = require('../ui/tooltips');
/** /**
* DetailView component implements a list of record layouts. * DetailView component implements a list of record layouts.
@ -227,8 +228,11 @@ DetailView.prototype.buildFieldDom = function(field, row) {
if (field.isNewField) { if (field.isNewField) {
return dom('div.g_record_detail_el.flexitem', return dom('div.g_record_detail_el.flexitem',
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
dom('div.g_record_detail_label', field.label), field.description.peek() ?
dom('div.g_record_detail_description', field.description), 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_value', field.value)
); );
} }
@ -258,8 +262,11 @@ DetailView.prototype.buildFieldDom = function(field, row) {
dom.autoDispose(isCellSelected), dom.autoDispose(isCellSelected),
dom.autoDispose(isCellActive), dom.autoDispose(isCellActive),
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
dom('div.g_record_detail_label', kd.text(field.displayLabel)), field.description.peek() ?
dom('div.g_record_detail_description', kd.text(field.description)), 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', dom('div.g_record_detail_value',
kd.toggleClass('scissors', isCopyActive), kd.toggleClass('scissors', isCopyActive),
kd.toggleClass('record-add', row._isAddRow), kd.toggleClass('record-add', row._isAddRow),

View File

@ -2,7 +2,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel';
import {urlState} from 'app/client/models/gristUrlState'; import {urlState} from 'app/client/models/gristUrlState';
import {docListHeader} from 'app/client/ui/DocMenuCss'; import {docListHeader} from 'app/client/ui/DocMenuCss';
import {GristTooltips, TooltipContentFunc} from 'app/client/ui/GristTooltips'; import {GristTooltips, TooltipContentFunc} from 'app/client/ui/GristTooltips';
import {withInfoTooltip} from 'app/client/ui/tooltips'; import { withQuestionMarkTooltip } from 'app/client/ui/tooltips';
import {mediaXSmall, theme} from 'app/client/ui2018/cssVars'; import {mediaXSmall, theme} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons'; import {icon} from 'app/client/ui2018/icons';
import {loadingDots, loadingSpinner} from 'app/client/ui2018/loaders'; import {loadingDots, loadingSpinner} from 'app/client/ui2018/loaders';
@ -284,7 +284,7 @@ function buildUsageMetric(options: MetricOptions, ...domArgs: DomElementArg[]) {
return cssUsageMetric( return cssUsageMetric(
cssMetricName( cssMetricName(
tooltipContentFunc tooltipContentFunc
? withInfoTooltip( ? withQuestionMarkTooltip(
cssOverflowableText(name, testId('name')), cssOverflowableText(name, testId('name')),
tooltipContentFunc() tooltipContentFunc()
) )

View File

@ -5,7 +5,7 @@ import {BEHAVIOR, ColumnRec} from 'app/client/models/entities/ColumnRec';
import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight'; import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight';
import {GristTooltips} from 'app/client/ui/GristTooltips'; import {GristTooltips} from 'app/client/ui/GristTooltips';
import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
import {withInfoTooltip} from 'app/client/ui/tooltips'; import { withQuestionMarkTooltip } from 'app/client/ui/tooltips';
import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas';
import {textButton} from 'app/client/ui2018/buttons'; import {textButton} from 'app/client/ui2018/buttons';
import {testId, theme} from 'app/client/ui2018/cssVars'; import {testId, theme} from 'app/client/ui2018/cssVars';
@ -371,7 +371,7 @@ export function buildFormulaConfig(
dom.prop("disabled", disableOtherActions), dom.prop("disabled", disableOtherActions),
testId("field-set-formula") testId("field-set-formula")
)), )),
cssRow(withInfoTooltip( cssRow(withQuestionMarkTooltip(
textButton( textButton(
t("Set trigger formula"), t("Set trigger formula"),
dom.on("click", setTrigger), dom.on("click", setTrigger),
@ -424,7 +424,7 @@ export function buildFormulaConfig(
// Else offer a way to convert to trigger formula. // Else offer a way to convert to trigger formula.
dom.maybe((use) => !(use(maybeTrigger) || use(origColumn.hasTriggerFormula)), () => [ dom.maybe((use) => !(use(maybeTrigger) || use(origColumn.hasTriggerFormula)), () => [
cssEmptySeparator(), cssEmptySeparator(),
cssRow(withInfoTooltip( cssRow(withQuestionMarkTooltip(
textButton( textButton(
t("Set trigger formula"), t("Set trigger formula"),
dom.on("click", convertDataColumnToTriggerColumn), dom.on("click", convertDataColumnToTriggerColumn),

View File

@ -5,7 +5,7 @@ import { reportError } from 'app/client/models/AppModel';
import { ColumnRec, TableRec, ViewSectionRec } from 'app/client/models/DocModel'; import { ColumnRec, TableRec, ViewSectionRec } from 'app/client/models/DocModel';
import { GristTooltips } from 'app/client/ui/GristTooltips'; import { GristTooltips } from 'app/client/ui/GristTooltips';
import { linkId, NoLink } from 'app/client/ui/selectBy'; import { linkId, NoLink } from 'app/client/ui/selectBy';
import { withInfoTooltip } from 'app/client/ui/tooltips'; import { withQuestionMarkTooltip } from 'app/client/ui/tooltips';
import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes'; import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes';
import { bigPrimaryButton } from "app/client/ui2018/buttons"; import { bigPrimaryButton } from "app/client/ui2018/buttons";
import { theme, vars } from "app/client/ui2018/cssVars"; import { theme, vars } from "app/client/ui2018/cssVars";
@ -358,7 +358,7 @@ export class PageWidgetSelect extends Disposable {
cssFooterContent( cssFooterContent(
// If _selectByOptions exists and has more than then "NoLinkOption", show the selector. // If _selectByOptions exists and has more than then "NoLinkOption", show the selector.
dom.maybe((use) => this._selectByOptions && use(this._selectByOptions).length > 1, () => dom.maybe((use) => this._selectByOptions && use(this._selectByOptions).length > 1, () =>
withInfoTooltip( withQuestionMarkTooltip(
cssSelectBy( cssSelectBy(
cssSmallLabel('SELECT BY'), cssSmallLabel('SELECT BY'),
dom.update(cssSelect(this._value.link, this._selectByOptions!), dom.update(cssSelect(this._value.link, this._selectByOptions!),

View File

@ -5,7 +5,7 @@ import {docUrl, urlState} from 'app/client/models/gristUrlState';
import {GristTooltips} from 'app/client/ui/GristTooltips'; import {GristTooltips} from 'app/client/ui/GristTooltips';
import {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu'; import {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu';
import {sendToDrive} from 'app/client/ui/sendToDrive'; import {sendToDrive} from 'app/client/ui/sendToDrive';
import {hoverTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; import { hoverTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips';
import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss';
import {primaryButton} from 'app/client/ui2018/buttons'; import {primaryButton} from 'app/client/ui2018/buttons';
import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars'; import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars';
@ -207,7 +207,7 @@ function menuWorkOnCopy(pageModel: DocPageModel) {
return [ return [
menuItem(makeUnsavedCopy, t("Work on a Copy"), testId('work-on-copy')), menuItem(makeUnsavedCopy, t("Work on a Copy"), testId('work-on-copy')),
menuText( menuText(
withInfoTooltip( withQuestionMarkTooltip(
t("Edit without affecting the original"), t("Edit without affecting the original"),
GristTooltips.workOnACopy(), GristTooltips.workOnACopy(),
{tooltipMenuOptions: {attach: null}} {tooltipMenuOptions: {attach: null}}

View File

@ -28,7 +28,7 @@ import {UserManagerModel, UserManagerModelImpl} from 'app/client/models/UserMana
import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel'; import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel';
import {GristTooltips} from 'app/client/ui/GristTooltips'; import {GristTooltips} from 'app/client/ui/GristTooltips';
import {shadowScroll} from 'app/client/ui/shadowScroll'; import {shadowScroll} from 'app/client/ui/shadowScroll';
import {hoverTooltip, ITooltipControl, showTransientTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; import { hoverTooltip, ITooltipControl, showTransientTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips';
import {createUserImage} from 'app/client/ui/UserImage'; import {createUserImage} from 'app/client/ui/UserImage';
import {cssMemberBtn, cssMemberImage, cssMemberListItem, import {cssMemberBtn, cssMemberImage, cssMemberListItem,
cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem, cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem,
@ -169,7 +169,7 @@ function buildUserManagerModal(
testId('um-cancel') testId('um-cancel')
), ),
(model.resourceType === 'document' && model.gristDoc && !model.isPersonal (model.resourceType === 'document' && model.gristDoc && !model.isPersonal
? withInfoTooltip( ? withQuestionMarkTooltip(
cssLink({href: urlState().makeUrl({docPage: 'acl'})}, cssLink({href: urlState().makeUrl({docPage: 'acl'})},
dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''), dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''),
'Open Access Rules', 'Open Access Rules',

View File

@ -9,7 +9,7 @@ import {prepareForTransition} from 'app/client/ui/transitions';
import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {testId, theme, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons'; import {icon} from 'app/client/ui2018/icons';
import {menuCssClass} from 'app/client/ui2018/menus'; import {menuCssClass} from 'app/client/ui2018/menus';
import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs'; import { dom, DomContents, DomElementArg, DomElementMethod, IDomArgs, styled } from 'grainjs';
import Popper from 'popper.js'; import Popper from 'popper.js';
import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel'; import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel';
@ -240,43 +240,59 @@ export function tooltipCloseButton(ctl: ITooltipControl): HTMLElement {
} }
/** /**
* Renders an info icon that shows a tooltip with the specified `content` on click. * Renders an icon that shows a tooltip with the specified `content` on click.
*/ */
function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { function iconTooltip(
return cssInfoTooltipButton('?', cssStyledFunc: (...args: IDomArgs<HTMLElement>) => HTMLElement,
(elem) => { tooltipButtonContent: HTMLElement,
setPopupToCreateDom( content: DomContents,
elem, menuOptions?: IMenuOptions,
(ctl) => { ...domArgs: DomElementArg[]
return cssInfoTooltipPopup( ) {
cssInfoTooltipPopupCloseButton( return cssStyledFunc(tooltipButtonContent, (elem) => {
icon('CrossSmall'), setPopupToCreateDom(
dom.on('click', () => ctl.close()), elem,
testId('info-tooltip-close'), (ctl) => {
), return cssInfoTooltipPopup(
cssInfoTooltipPopupBody( cssInfoTooltipPopupCloseButton(
content, icon("CrossSmall"),
testId('info-tooltip-popup-body'), dom.on("click", () => ctl.close()),
), testId("info-tooltip-close")
dom.cls(menuCssClass), ),
dom.cls(cssMenu.className), cssInfoTooltipPopupBody(content, testId("info-tooltip-popup-body")),
dom.onKeyDown({ dom.cls(menuCssClass),
Enter: () => ctl.close(), dom.cls(cssMenu.className),
Escape: () => ctl.close(), dom.onKeyDown({
}), Enter: () => ctl.close(),
(popup) => { setTimeout(() => popup.focus(), 0); }, Escape: () => ctl.close(),
testId('info-tooltip-popup'), }),
); (popup) => {
}, setTimeout(() => popup.focus(), 0);
{...defaultMenuOptions, ...{placement: 'bottom-end'}, ...menuOptions}, },
); testId("info-tooltip-popup")
}, );
testId('info-tooltip'), },
...domArgs, { ...defaultMenuOptions, ...{ placement: "bottom-end" }, ...menuOptions }
); );
});
} }
export interface WithInfoTooltipOptions { 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 {
domArgs?: DomElementArg[]; domArgs?: DomElementArg[];
tooltipButtonDomArgs?: DomElementArg[]; tooltipButtonDomArgs?: DomElementArg[];
tooltipMenuOptions?: IMenuOptions; tooltipMenuOptions?: IMenuOptions;
@ -296,17 +312,30 @@ export interface WithInfoTooltipOptions {
* *
* Usage: * Usage:
* *
* withInfoTooltip( * withQuestionMarkTooltip(
* dom('div', 'Hello World!'), * dom('div', 'Hello World!'),
* dom('p', 'This is some text to show inside the tooltip.'), * 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( export function withInfoTooltip(
domContents: DomContents, domContents: DomContents,
tooltipContent: DomContents, tooltipContent: DomContents,
options: WithInfoTooltipOptions = {}, options: WithIconTooltipOptions = {},
) { ) {
const {domArgs, tooltipButtonDomArgs, tooltipMenuOptions} = options; const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options;
return cssDomWithTooltip( return cssDomWithTooltip(
domContents, domContents,
infoTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), infoTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs),
@ -347,7 +376,7 @@ const cssTooltipCloseButton = styled('div', `
} }
`); `);
const cssInfoTooltipButton = styled('div', ` const cssQuestionMark = styled('div', `
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
@ -366,6 +395,25 @@ const cssInfoTooltipButton = 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', ` const cssInfoTooltipPopup = styled('div', `
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -605,10 +605,10 @@ export const theme = {
menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'), menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'),
menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate), menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate),
/* Info Toggles */ /* Info Button */
infoToggleFg: new CustomProp('theme-info-toggle-fg', undefined, "#8F8F8F"), infoButtonFg: new CustomProp('theme-info-button-fg', undefined, "#8F8F8F"),
infoToggleHoverFg: new CustomProp('theme-info-toggle-hover-fg', undefined, "#707070"), infoButtonHoverFg: new CustomProp('theme-info-button-hover-fg', undefined, "#707070"),
infoToggleActiveFg: new CustomProp('theme-info-toggle-active-fg', undefined, "#5C5C5C"), infoButtonActiveFg: new CustomProp('theme-info-button-active-fg', undefined, "#5C5C5C"),
/* Button Groups */ /* Button Groups */
buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark), buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark),

View File

@ -6,7 +6,7 @@ import {RuleOwner} from 'app/client/models/RuleOwner';
import {Style} from 'app/client/models/Styles'; import {Style} from 'app/client/models/Styles';
import {cssFieldFormula} from 'app/client/ui/FieldConfig'; import {cssFieldFormula} from 'app/client/ui/FieldConfig';
import {GristTooltips} from 'app/client/ui/GristTooltips'; import {GristTooltips} from 'app/client/ui/GristTooltips';
import {withInfoTooltip} from 'app/client/ui/tooltips'; import { withQuestionMarkTooltip } from 'app/client/ui/tooltips';
import {textButton} from 'app/client/ui2018/buttons'; import {textButton} from 'app/client/ui2018/buttons';
import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect';
import {theme, vars} from 'app/client/ui2018/cssVars'; import {theme, vars} from 'app/client/ui2018/cssVars';
@ -71,7 +71,7 @@ export class ConditionalStyle extends Disposable {
return [ return [
cssRow( cssRow(
{ style: 'margin-top: 16px' }, { style: 'margin-top: 16px' },
withInfoTooltip( withQuestionMarkTooltip(
textButton( textButton(
t('Add conditional style'), t('Add conditional style'),
testId('add-conditional-style'), testId('add-conditional-style'),