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;
}
.g_record_detail_description {
min-height: 1rem;
color: #666;
font-size: 0.9rem;
font-weight: normal;
margin-bottom: 2px;
}
.g_record_detail_value {
position: relative;
min-height: 16px;
@ -237,7 +230,7 @@
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);
color: var(--grist-theme-card-form-label, var(--grist-color-slate));
font-weight: bold;
@ -248,16 +241,11 @@
margin-right: -8px;
}
.detail_theme_field_form>.g_record_detail_description {
font-size: var(--grist-small-font-size);
color: var(--grist-theme-card-form-label, var(--grist-color-slate));
font-weight: normal;
min-height: 0px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: -8px;
.detail_theme_field_form .info_toggle_icon {
width: 13px;
height: 13px;
}
/* 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
* 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 {RowContextMenu} = require('../ui/RowContextMenu');
const {parsePasteForView} = require("./BaseView2");
const {withInfoTooltip} = require('../ui/tooltips');
/**
* DetailView component implements a list of record layouts.
@ -227,8 +228,11 @@ 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(); }),
dom('div.g_record_detail_label', field.label),
dom('div.g_record_detail_description', field.description),
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)
);
}
@ -258,8 +262,11 @@ DetailView.prototype.buildFieldDom = function(field, row) {
dom.autoDispose(isCellSelected),
dom.autoDispose(isCellActive),
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
dom('div.g_record_detail_label', kd.text(field.displayLabel)),
dom('div.g_record_detail_description', kd.text(field.description)),
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',
kd.toggleClass('scissors', isCopyActive),
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 {docListHeader} from 'app/client/ui/DocMenuCss';
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 {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
? withInfoTooltip(
? withQuestionMarkTooltip(
cssOverflowableText(name, testId('name')),
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 {GristTooltips} from 'app/client/ui/GristTooltips';
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 {textButton} from 'app/client/ui2018/buttons';
import {testId, theme} from 'app/client/ui2018/cssVars';
@ -371,7 +371,7 @@ export function buildFormulaConfig(
dom.prop("disabled", disableOtherActions),
testId("field-set-formula")
)),
cssRow(withInfoTooltip(
cssRow(withQuestionMarkTooltip(
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(withInfoTooltip(
cssRow(withQuestionMarkTooltip(
textButton(
t("Set trigger formula"),
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 { GristTooltips } from 'app/client/ui/GristTooltips';
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 { 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, () =>
withInfoTooltip(
withQuestionMarkTooltip(
cssSelectBy(
cssSmallLabel('SELECT BY'),
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 {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu';
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 {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(
withInfoTooltip(
withQuestionMarkTooltip(
t("Edit without affecting the original"),
GristTooltips.workOnACopy(),
{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 {GristTooltips} from 'app/client/ui/GristTooltips';
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 {cssMemberBtn, cssMemberImage, cssMemberListItem,
cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem,
@ -169,7 +169,7 @@ function buildUserManagerModal(
testId('um-cancel')
),
(model.resourceType === 'document' && model.gristDoc && !model.isPersonal
? withInfoTooltip(
? withQuestionMarkTooltip(
cssLink({href: urlState().makeUrl({docPage: 'acl'})},
dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''),
'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 {icon} from 'app/client/ui2018/icons';
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 {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[]) {
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 iconTooltip(
cssStyledFunc: (...args: IDomArgs<HTMLElement>) => 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 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[];
tooltipButtonDomArgs?: DomElementArg[];
tooltipMenuOptions?: IMenuOptions;
@ -296,17 +312,30 @@ export interface WithInfoTooltipOptions {
*
* Usage:
*
* withInfoTooltip(
* withQuestionMarkTooltip(
* 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: WithInfoTooltipOptions = {},
options: WithIconTooltipOptions = {},
) {
const {domArgs, tooltipButtonDomArgs, tooltipMenuOptions} = options;
const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options;
return cssDomWithTooltip(
domContents,
infoTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs),
@ -347,7 +376,7 @@ const cssTooltipCloseButton = styled('div', `
}
`);
const cssInfoTooltipButton = styled('div', `
const cssQuestionMark = styled('div', `
flex-shrink: 0;
display: flex;
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', `
display: flex;
flex-direction: column;

View File

@ -605,10 +605,10 @@ export const theme = {
menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'),
menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate),
/* Info Toggles */
infoToggleFg: new CustomProp('theme-info-toggle-fg', undefined, "#8F8F8F"),
infoToggleHoverFg: new CustomProp('theme-info-toggle-hover-fg', undefined, "#707070"),
infoToggleActiveFg: new CustomProp('theme-info-toggle-active-fg', undefined, "#5C5C5C"),
/* Info Button */
infoButtonFg: new CustomProp('theme-info-button-fg', undefined, "#8F8F8F"),
infoButtonHoverFg: new CustomProp('theme-info-button-hover-fg', undefined, "#707070"),
infoButtonActiveFg: new CustomProp('theme-info-button-active-fg', undefined, "#5C5C5C"),
/* Button Groups */
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 {cssFieldFormula} from 'app/client/ui/FieldConfig';
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 {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' },
withInfoTooltip(
withQuestionMarkTooltip(
textButton(
t('Add conditional style'),
testId('add-conditional-style'),