mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add new color select to the app
Summary: - Fix transparency support on color select - Fix z-index conflicts with color select and right panel - Makes widget's default text color visible to color select Test Plan: - Updates nbrowser/CellColor and browser/Widget.test to support new interface. Should not cause regression. Reviewers: paulfitz, dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2735
This commit is contained in:
parent
4ab096d179
commit
1995a96178
@ -70,7 +70,7 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field"> {
|
||||
disableModify: ko.Computed<boolean>;
|
||||
disableEditData: ko.Computed<boolean>;
|
||||
|
||||
textColor: modelUtil.KoSaveableObservable<string>;
|
||||
textColor: modelUtil.KoSaveableObservable<string|undefined>;
|
||||
fillColor: modelUtil.KoSaveableObservable<string>;
|
||||
|
||||
// Helper which adds/removes/updates field's displayCol to match the formula.
|
||||
@ -201,8 +201,7 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
this.disableModify = ko.pureComputed(() => this.column().disableModify());
|
||||
this.disableEditData = ko.pureComputed(() => this.column().disableEditData());
|
||||
|
||||
this.textColor = modelUtil.fieldWithDefault(
|
||||
this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>, '');
|
||||
this.textColor = this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>;
|
||||
|
||||
const fillColorProp = modelUtil.fieldWithDefault(
|
||||
this.widgetOptionsJson.prop('fillColor') as modelUtil.KoSaveableObservable<string>, "#FFFFFF00");
|
||||
|
@ -2,7 +2,7 @@ import { darker, lighter } from "app/client/ui2018/ColorPalette";
|
||||
import { colors, testId, vars } from 'app/client/ui2018/cssVars';
|
||||
import { icon } from "app/client/ui2018/icons";
|
||||
import { Computed, Disposable, dom, DomArg, Observable, onKeyDown, styled } from "grainjs";
|
||||
import { IOpenController, setPopupToCreateDom } from "popweasel";
|
||||
import { defaultMenuOptions, IOpenController, setPopupToCreateDom } from "popweasel";
|
||||
|
||||
/**
|
||||
* colorSelect allows to select color for both fill and text cell color. It allows for fast
|
||||
@ -17,7 +17,7 @@ export function colorSelect(textColor: Observable<string>, fillColor: Observable
|
||||
cssButtonIcon(
|
||||
'T',
|
||||
dom.style('color', textColor),
|
||||
dom.style('background-color', fillColor),
|
||||
dom.style('background-color', (use) => use(fillColor).slice(0, 7)),
|
||||
cssLightBorder.cls(''),
|
||||
testId('btn-icon'),
|
||||
),
|
||||
@ -28,10 +28,7 @@ export function colorSelect(textColor: Observable<string>, fillColor: Observable
|
||||
);
|
||||
|
||||
const domCreator = (ctl: IOpenController) => buildColorPicker(ctl, textColor, fillColor, onSave);
|
||||
setPopupToCreateDom(selectBtn, domCreator, {
|
||||
trigger: ['click'],
|
||||
placement: 'bottom-end',
|
||||
});
|
||||
setPopupToCreateDom(selectBtn, domCreator, {...defaultMenuOptions, placement: 'bottom-end'});
|
||||
|
||||
return selectBtn;
|
||||
}
|
||||
@ -72,13 +69,13 @@ function buildColorPicker(ctl: IOpenController, textColor: Observable<string>, f
|
||||
return cssContainer(
|
||||
dom.create(PickerComponent, fillColorModel, {
|
||||
colorSquare: colorSquare(),
|
||||
title: 'Fill',
|
||||
title: 'fill',
|
||||
defaultMode: 'lighter'
|
||||
}),
|
||||
cssVSpacer(),
|
||||
dom.create(PickerComponent, textColorModel, {
|
||||
colorSquare: colorSquare('T'),
|
||||
title: 'Text',
|
||||
title: 'text',
|
||||
defaultMode: 'darker'
|
||||
}),
|
||||
|
||||
@ -132,7 +129,7 @@ class PickerModel extends Disposable {
|
||||
|
||||
class PickerComponent extends Disposable {
|
||||
|
||||
private _color = Computed.create(this, this._model.obs, (use, val) => val.toUpperCase());
|
||||
private _color = Computed.create(this, this._model.obs, (use, val) => val.toUpperCase().slice(0, 7));
|
||||
private _mode = Observable.create<'darker'|'lighter'>(this, this._guessMode());
|
||||
|
||||
constructor(private _model: PickerModel, private _options: PickerComponentOptions) {
|
||||
@ -155,7 +152,7 @@ class PickerComponent extends Disposable {
|
||||
),
|
||||
// TODO: make it possible to type in hex value.
|
||||
cssHexBox(
|
||||
dom.attr('value', (use) => use(this._color || '#000000')),
|
||||
dom.attr('value', this._color),
|
||||
{readonly: true},
|
||||
dom.on('click', (ev, e) => e.select()),
|
||||
testId(`${title}-hex`),
|
||||
@ -244,6 +241,7 @@ const cssHeaderRow = styled('div', `
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
text-transform: capitalize;
|
||||
`);
|
||||
|
||||
|
||||
@ -265,6 +263,8 @@ const cssContainer = styled('div', `
|
||||
padding: 18px 16px;
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 16px 0 rgba(38,38,51,0.6);
|
||||
z-index: 20;
|
||||
margin: 2px 0;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
@ -318,4 +318,5 @@ const cssSelectBtn = styled('div', `
|
||||
padding: 5px 9px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
`);
|
||||
|
@ -1,24 +1,28 @@
|
||||
var dispose = require('../lib/dispose');
|
||||
const ko = require('knockout');
|
||||
const {fromKo} = require('grainjs');
|
||||
const {Computed, fromKo} = require('grainjs');
|
||||
const ValueFormatter = require('app/common/ValueFormatter');
|
||||
|
||||
const {cssLabel, cssRow} = require('app/client/ui/RightPanel');
|
||||
const {colorSelect} = require('app/client/ui2018/buttonSelect');
|
||||
const {testId} = require('app/client/ui2018/cssVars');
|
||||
const {cssHalfWidth, cssInlineLabel} = require('app/client/widgets/NewAbstractWidget');
|
||||
const {colorSelect} = require('app/client/ui2018/ColorSelect');
|
||||
|
||||
/**
|
||||
* AbstractWidget - The base of the inheritance tree for widgets.
|
||||
* @param {Function} field - The RowModel for this view field.
|
||||
* @param {string|undefined} options.defaultTextColor - A hex value to set the default text color
|
||||
* for the widget. Omit defaults to '#000000'.
|
||||
*/
|
||||
function AbstractWidget(field) {
|
||||
function AbstractWidget(field, opts = {}) {
|
||||
this.field = field;
|
||||
this.options = field.widgetOptionsJson;
|
||||
const {defaultTextColor = '#000000'} = opts;
|
||||
|
||||
this.valueFormatter = this.autoDispose(ko.computed(() => {
|
||||
return ValueFormatter.createFormatter(field.displayColModel().type(), this.options());
|
||||
}));
|
||||
|
||||
this.textColor = Computed.create(this, (use) => use(this.field.textColor) || defaultTextColor)
|
||||
.onWrite((val) => this.field.textColor(val === defaultTextColor ? undefined : val));
|
||||
}
|
||||
dispose.makeDisposable(AbstractWidget);
|
||||
|
||||
@ -49,21 +53,11 @@ AbstractWidget.prototype.buildColorConfigDom = function() {
|
||||
return [
|
||||
cssLabel('CELL COLOR'),
|
||||
cssRow(
|
||||
cssHalfWidth(
|
||||
colorSelect(
|
||||
fromKo(this.field.textColor),
|
||||
(val) => this.field.textColor.saveOnly(val),
|
||||
testId('text-color'),
|
||||
),
|
||||
cssInlineLabel('Text')
|
||||
),
|
||||
cssHalfWidth(
|
||||
colorSelect(
|
||||
fromKo(this.field.fillColor),
|
||||
(val) => this.field.fillColor.saveOnly(val),
|
||||
testId('fill-color'),
|
||||
),
|
||||
cssInlineLabel('Fill')
|
||||
colorSelect(
|
||||
this.textColor,
|
||||
fromKo(this.field.fillColor),
|
||||
// Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
|
||||
() => this.field.widgetOptionsJson.save()
|
||||
)
|
||||
)
|
||||
];
|
||||
|
@ -8,7 +8,7 @@ var AbstractWidget = require('./AbstractWidget');
|
||||
* CheckBox - A bi-state CheckBox widget
|
||||
*/
|
||||
function CheckBox(field) {
|
||||
AbstractWidget.call(this, field);
|
||||
AbstractWidget.call(this, field, {defaultTextColor: '#606060'});
|
||||
}
|
||||
dispose.makeDisposable(CheckBox);
|
||||
_.extend(CheckBox.prototype, AbstractWidget.prototype);
|
||||
|
@ -421,7 +421,7 @@ export class FieldBuilder extends Disposable {
|
||||
if (this.isDisposed()) { return null; } // Work around JS errors during field removal.
|
||||
const cellDom = widget ? widget.buildDom(row) : buildErrorDom(row, this.field);
|
||||
return dom(cellDom, kd.toggleClass('has_cursor', isActive),
|
||||
kd.style('--grist-cell-color', this.field.textColor),
|
||||
kd.style('--grist-cell-color', () => this.field.textColor() || ''),
|
||||
kd.style('--grist-cell-background-color', this.field.fillColor));
|
||||
})
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ import {dom, styled} from 'grainjs';
|
||||
*/
|
||||
export class HyperLinkTextBox extends NTextBox {
|
||||
constructor(field: ViewFieldRec) {
|
||||
super(field);
|
||||
super(field, {defaultTextColor: colors.lightGreen.value});
|
||||
}
|
||||
|
||||
public buildDom(row: DataRowModel) {
|
||||
|
@ -4,7 +4,7 @@ import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
||||
import {cssRow} from 'app/client/ui/RightPanel';
|
||||
import {alignmentSelect, makeButtonSelect} from 'app/client/ui2018/buttonSelect';
|
||||
import {testId} from 'app/client/ui2018/cssVars';
|
||||
import {NewAbstractWidget} from 'app/client/widgets/NewAbstractWidget';
|
||||
import {NewAbstractWidget, Options} from 'app/client/widgets/NewAbstractWidget';
|
||||
import {dom, DomContents, fromKo, Observable} from 'grainjs';
|
||||
|
||||
/**
|
||||
@ -14,8 +14,8 @@ export class NTextBox extends NewAbstractWidget {
|
||||
protected alignment: Observable<string>;
|
||||
protected wrapping: Observable<boolean>;
|
||||
|
||||
constructor(field: ViewFieldRec) {
|
||||
super(field);
|
||||
constructor(field: ViewFieldRec, options: Options = {}) {
|
||||
super(field, options);
|
||||
|
||||
this.alignment = fromKoSave<string>(this.options.prop('alignment'));
|
||||
this.wrapping = fromKo(this.field.wrapping);
|
||||
|
@ -7,13 +7,17 @@ import {DocData} from 'app/client/models/DocData';
|
||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
||||
import {SaveableObjObservable} from 'app/client/models/modelUtil';
|
||||
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
|
||||
import {colorSelect} from 'app/client/ui2018/buttonSelect';
|
||||
import {colors, testId} from 'app/client/ui2018/cssVars';
|
||||
import {colorSelect} from 'app/client/ui2018/ColorSelect';
|
||||
import {BaseFormatter, createFormatter} from 'app/common/ValueFormatter';
|
||||
import {Disposable, DomContents, fromKo, Observable, styled} from 'grainjs';
|
||||
import {Computed, Disposable, DomContents, fromKo, Observable} from 'grainjs';
|
||||
import * as ko from 'knockout';
|
||||
|
||||
|
||||
export interface Options {
|
||||
// A hex value to set the default widget text color. Default to '#000000' if omitted.
|
||||
defaultTextColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NewAbstractWidget - The base of the inheritance tree for widgets.
|
||||
* @param {Function} field - The RowModel for this view field.
|
||||
@ -31,10 +35,13 @@ export abstract class NewAbstractWidget extends Disposable {
|
||||
protected textColor: Observable<string>;
|
||||
protected fillColor: Observable<string>;
|
||||
|
||||
constructor(protected field: ViewFieldRec) {
|
||||
constructor(protected field: ViewFieldRec, opts: Options = {}) {
|
||||
super();
|
||||
const {defaultTextColor = '#000000'} = opts;
|
||||
this.options = field.widgetOptionsJson;
|
||||
this.textColor = fromKo(this.field.textColor);
|
||||
this.textColor = Computed.create(this, (use) => (
|
||||
use(this.field.textColor) || defaultTextColor
|
||||
)).onWrite((val) => this.field.textColor(val === defaultTextColor ? undefined : val));
|
||||
this.fillColor = fromKo(this.field.fillColor);
|
||||
|
||||
// Note that its easier to create a knockout computed from the several knockout observables,
|
||||
@ -59,21 +66,11 @@ export abstract class NewAbstractWidget extends Disposable {
|
||||
return [
|
||||
cssLabel('CELL COLOR'),
|
||||
cssRow(
|
||||
cssHalfWidth(
|
||||
colorSelect(
|
||||
this.textColor,
|
||||
(val) => this.field.textColor.saveOnly(val),
|
||||
testId('text-color')
|
||||
),
|
||||
cssInlineLabel('Text'),
|
||||
),
|
||||
cssHalfWidth(
|
||||
colorSelect(
|
||||
this.fillColor,
|
||||
(val) => this.field.fillColor.saveOnly(val),
|
||||
testId('fill-color')
|
||||
),
|
||||
cssInlineLabel('Fill')
|
||||
colorSelect(
|
||||
this.textColor,
|
||||
this.fillColor,
|
||||
// Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
|
||||
() => this.field.widgetOptionsJson.save()
|
||||
)
|
||||
)
|
||||
];
|
||||
@ -98,14 +95,3 @@ export abstract class NewAbstractWidget extends Disposable {
|
||||
*/
|
||||
protected _getDocComm(): DocComm { return this._getDocData().docComm; }
|
||||
}
|
||||
|
||||
export const cssHalfWidth = styled('div', `
|
||||
display: flex;
|
||||
flex: 1 1 50%;
|
||||
align-items: center;
|
||||
`);
|
||||
|
||||
export const cssInlineLabel = styled('span', `
|
||||
margin: 0 8px;
|
||||
color: ${colors.dark};
|
||||
`);
|
||||
|
@ -8,7 +8,7 @@ var AbstractWidget = require('./AbstractWidget');
|
||||
* Switch - A bi-state Switch widget
|
||||
*/
|
||||
function Switch(field) {
|
||||
AbstractWidget.call(this, field);
|
||||
AbstractWidget.call(this, field, {defaultTextColor: '#2CB0AF'});
|
||||
}
|
||||
dispose.makeDisposable(Switch);
|
||||
_.extend(Switch.prototype, AbstractWidget.prototype);
|
||||
|
Loading…
Reference in New Issue
Block a user