Browse Source

UI: Color picker design improvement

pull/10290/head
Artem Dzhereleiko 2 years ago
parent
commit
e1f8f51900
  1. 8
      ui-ngx/src/app/shared/components/color-input.component.ts
  2. 26
      ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.html
  3. 17
      ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.scss
  4. 16
      ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.ts
  5. 57
      ui-ngx/src/app/shared/components/color-picker/color-picker.component.html
  6. 49
      ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss
  7. 4
      ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts
  8. 36
      ui-ngx/src/app/shared/components/color-picker/hex-input.component.html
  9. 48
      ui-ngx/src/app/shared/components/color-picker/hex-input.component.scss
  10. 75
      ui-ngx/src/app/shared/components/color-picker/hex-input.component.ts
  11. 8
      ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html
  12. 28
      ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.scss
  13. 3
      ui-ngx/src/app/shared/components/popover.component.ts
  14. 9
      ui-ngx/src/app/shared/components/popover.service.ts
  15. 16
      ui-ngx/src/app/shared/shared.module.ts

8
ui-ngx/src/app/shared/components/color-input.component.ts

@ -189,13 +189,13 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro
this.popoverService.hidePopover(trigger);
} else {
const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ColorPickerPanelComponent, ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null,
this.viewContainerRef, ColorPickerPanelComponent, ['left'], true, null,
{
color: this.colorFormGroup.get('color').value,
colorClearButton: this.colorClearButton
colorClearButton: this.colorClearButton,
colorCancelButton: true
},
{},
{}, {}, true);
{}, {}, {}, false, () => {}, {padding: '12px 4px 12px 12px'});
colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover;
colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => {
colorPickerPopover.hide();

26
ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.html

@ -16,8 +16,7 @@
-->
<div class="tb-color-picker-panel">
<div class="tb-color-picker-title" translate>color.color</div>
<tb-color-picker [formControl]="colorPickerControl"></tb-color-picker>
<tb-color-picker class="tb-color-picker" [formControl]="colorPickerControl"></tb-color-picker>
<div class="tb-color-picker-panel-buttons">
<button *ngIf="colorClearButton"
mat-button
@ -27,12 +26,21 @@
[disabled]="!colorPickerControl.value">
{{ 'action.clear' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="selectColor()"
[disabled]="colorPickerControl.invalid || !colorPickerControl.dirty">
{{ 'action.select' | translate }}
</button>
<span fxFlex></span>
<div fxLayoutGap="8px">
<button *ngIf="colorCancelButton"
mat-button
type="button"
(click)="cancelColor()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="selectColor()"
[disabled]="colorPickerControl.invalid || !colorPickerControl.dirty">
{{ 'action.select' | translate }}
</button>
</div>
</div>
</div>

17
ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.scss

@ -13,11 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../scss/constants";
.tb-color-picker-panel {
width: 328px;
width: 342px;
display: flex;
flex-direction: column;
gap: 16px;
max-height: calc(100vh - 24px);
min-height: 100%;
@media #{$mat-sm} {
width: 578px;
}
.tb-color-picker-title {
font-size: 16px;
font-weight: 500;
@ -25,12 +31,17 @@
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-color-picker {
padding-right: 4px;
overflow-y: scroll;
}
.tb-color-picker-panel-buttons {
height: 60px;
padding: 8px 8px 0 0;
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
justify-content: space-between;
align-items: flex-end;
}
}

16
ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.ts

@ -38,12 +38,19 @@ export class ColorPickerPanelComponent extends PageComponent implements OnInit {
@coerceBoolean()
colorClearButton = false;
@Input()
@coerceBoolean()
colorCancelButton = false;
@Input()
popover: TbPopoverComponent<ColorPickerPanelComponent>;
@Output()
colorSelected = new EventEmitter<string>();
@Output()
colorCancelDialog = new EventEmitter();
colorPickerControl: UntypedFormControl;
constructor(protected store: Store<AppState>) {
@ -61,4 +68,11 @@ export class ColorPickerPanelComponent extends PageComponent implements OnInit {
clearColor() {
this.colorSelected.emit(null);
}
}
cancelColor() {
if (this.popover) {
this.popover.hide();
} else {
this.colorCancelDialog.emit();
}
}}

57
ui-ngx/src/app/shared/components/color-picker/color-picker.component.html

@ -15,35 +15,38 @@
limitations under the License.
-->
<saturation-component class="saturation-component" [hue]="control.hue" [(color)]="control.value"></saturation-component>
<div class="control-component">
<indicator-component class="indicator-component"
[colorType]="presentations[presentationControl.value]"
[color]="control.value">
</indicator-component>
<div class="hue-alpha-range">
<hue-component [(hue)]="control.hue" [(color)]="control.value"></hue-component>
<alpha-component [(color)]="control.value"></alpha-component>
</div>
</div>
<div class="color-input-block">
<mat-select class="presentation-select" [formControl]="presentationControl">
<mat-option [value]="0">HEX</mat-option>
<mat-option [value]="1">RGBA</mat-option>
<mat-option [value]="2">HSLA</mat-option>
</mat-select>
<div class="color-input" [ngSwitch]="presentations[presentationControl.value]">
<rgba-input-component *ngSwitchCase="'rgba'" label
[(color)]="control.value" [(hue)]="control.hue"></rgba-input-component>
<hsla-input-component *ngSwitchCase="'hsla'" label
[(color)]="control.value" [(hue)]="control.hue"></hsla-input-component>
<hex-input-component *ngSwitchCase="'hex'" label prefix="#" [(color)]="control.value"
[(hue)]="control.hue"></hex-input-component>
<div class="color-input-container">
<saturation-component class="saturation-component" [hue]="control.hue" [(color)]="control.value"></saturation-component>
<div class="control-input-container">
<div class="control-component">
<indicator-component class="indicator-component"
[colorType]="presentations[presentationControl.value]"
[color]="control.value">
</indicator-component>
<div class="hue-alpha-range">
<hue-component [(hue)]="control.hue" [(color)]="control.value"></hue-component>
<alpha-component [(color)]="control.value"></alpha-component>
</div>
</div>
<div class="color-input-block">
<mat-select class="presentation-select" [formControl]="presentationControl">
<mat-option [value]="0">HEX</mat-option>
<mat-option [value]="1">RGBA</mat-option>
<mat-option [value]="2">HSLA</mat-option>
</mat-select>
<div class="color-input" [ngSwitch]="presentations[presentationControl.value]">
<rgba-input-component *ngSwitchCase="'rgba'" label
[(color)]="control.value" [(hue)]="control.hue"></rgba-input-component>
<hsla-input-component *ngSwitchCase="'hsla'" label
[(color)]="control.value" [(hue)]="control.hue"></hsla-input-component>
<tb-hex-input *ngSwitchCase="'hex'" [(color)]="control.value"></tb-hex-input>
</div>
</div>
</div>
</div>
<div class="color-presets-block">
<color-presets-component class="color-presets-component" columns="11" [colorPresets]="colorPresets" [(color)]="control.value" [(hue)]="control.hue"></color-presets-component>
<color-presets-component class="color-presets-component" [columns]="6" [colorPresets]="colorPresets" [(color)]="control.value" [(hue)]="control.hue"></color-presets-component>
</div>

49
ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../scss/constants";
:host {
width: 100%;
display: flex;
@ -20,10 +22,34 @@
gap: 32px;
overflow: auto;
.color-input-container {
display: flex;
flex-direction: column;
gap: 32px;
@media #{$mat-sm} {
flex-direction: row;
gap: 12px;
}
}
.control-input-container {
display: flex;
flex-direction: column;
gap: 32px;
@media #{$mat-sm} {
width: 100%;
justify-content: center;
}
}
.saturation-component {
width: 100%;
height: 238px;
min-height: 80px;
min-height: 160px;
border-radius: 8px;
@media #{$mat-sm} {
max-height: 160px;
}
}
.control-component {
@ -54,7 +80,9 @@
}
.color-input-block {
height: 56px;
display: flex;
align-items: center;
gap: 20px;
.presentation-select {
@ -74,8 +102,13 @@
.color-presets-block {
.color-presets-component {
display: flex;
flex-direction: column;
gap: 12px;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
gap: 8px;
@media #{$mat-xs} {
flex-direction: column;
}
}
}
}
@ -111,13 +144,21 @@
.color-presets-component {
.presets-row {
gap: 10px;
gap: 8px;
justify-content: space-between;
}
color-preset {
height: 20px;
width: 20px;
border-radius: 4px;
@media #{$mat-xs} {
height: 40px;
width: 48px;
}
@media #{$mat-sm} {
height: 40px;
width: 40px;
}
}
}
}

4
ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts

@ -31,8 +31,8 @@ export enum ColorType {
}
const colorPresetsHex =
['#435B63', '#F44336', '#E89623', '#F5DD00', '#8BC34A', '#4CAF50', '#009688', '#048AD3', '#673AB7', '#9C27B0', '#E91E63',
'#A1ADB1', '#F9A19B', '#FFD190', '#FFF59D', '#C5E1A4', '#A5D7A7', '#80CBC3', '#81C4E9', '#B39CDB', '#CD93D7', '#F48FB1'];
['#435B63', '#F44336', '#E89623', '#F5DD00', '#8BC34A', '#4CAF50', '#009688', '#048AD3', '#673AB7', '#9C27B0', '#E91E63', '#6F113A',
'#A1ADB1', '#F9A19B', '#FFD190', '#FFF59D', '#C5E1A4', '#A5D7A7', '#80CBC3', '#81C4E9', '#B39CDB', '#CD93D7', '#F48FB1', '#BC91A4'];
@Component({
selector: `tb-color-picker`,

36
ui-ngx/src/app/shared/components/color-picker/hex-input.component.html

@ -0,0 +1,36 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="hex-input-container">
<mat-form-field class="hex-input" subscriptSizing="dynamic">
<span matPrefix>{{prefixValue}}</span>
<input matInput #elRef type="text" [value]="value" (keyup)="onInputChange($event, elRef.value)">
<tb-copy-button
class="copy-button"
matSuffix
miniButton="false"
[copyText]="copyColor"
tooltipText="{{ 'action.copy' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
</mat-form-field>
<mat-form-field class="alpha-input" subscriptSizing="dynamic">
<input matInput #hue min="0" max="100" [value]="hueValue" (keyup)="onHueInputChange($event, hue.value)">
<span matSuffix>%</span>
</mat-form-field>
</div>

48
ui-ngx/src/app/shared/components/color-picker/hex-input.component.scss

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.hex-input-container {
display: flex;
gap: 8px;
}
.hex-input {
max-width: 190px;
}
.alpha-input {
min-width: 60px;
max-width: 60px;
}
::ng-deep {
.mdc-text-field--filled, .mat-mdc-form-field-focus-overlay {
&:before {
background-color: transparent !important;
}
}
.mat-mdc-form-field-icon-prefix, .mdc-line-ripple, .copy-button {
opacity: 0.4;
}
.alpha-input {
.mat-mdc-text-field-wrapper {
padding-left: 0;
}
}
}
}

75
ui-ngx/src/app/shared/components/color-picker/hex-input.component.ts

@ -0,0 +1,75 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { Color } from '@iplab/ngx-color-picker';
@Component({
selector: `tb-hex-input`,
templateUrl: `./hex-input.component.html`,
styleUrls: ['./hex-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HexInputComponent {
@Input()
public color: Color;
@Output()
public colorChange = new EventEmitter<Color>(false);
@Input()
public labelVisible = false;
@Input()
public prefixValue = '#';
public get value() {
return this.color ? this.color.toHexString(this.color.getRgba().alpha < 1).replace('#', '') : '';
}
public get copyColor() {
return this.prefixValue + this.value;
}
public get hueValue(): string {
return this.color ? Math.round(this.color.getRgba().alpha * 100).toString() : '';
}
public onHueInputChange(event: KeyboardEvent, inputValue: string): void {
const color = this.color.getRgba();
const alpha = +inputValue / 100;
if (color.getAlpha() !== alpha) {
const newColor = new Color().setRgba(color.red, color.green, color.blue, alpha).toHexString(true);
this.colorChange.emit(new Color(newColor));
}
}
public onInputChange(event: KeyboardEvent, inputValue: string): void {
const value = inputValue.toLowerCase();
if (
((event.keyCode === 13 || event.key.toLowerCase() === 'enter') && value.length === 3)
|| value.length === 6 || value.length === 8
) {
const hex = parseInt(value, 16);
const hexStr = hex.toString(16);
if (hexStr.padStart(value.length, '0') === value && this.value !== value) {
const newColor = new Color(`#${value}`);
this.colorChange.emit(newColor);
}
}
}
}

8
ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html

@ -16,14 +16,10 @@
-->
<div mat-dialog-content>
<button class="tb-close-button"
mat-icon-button
(click)="cancel()"
type="button">
<mat-icon>close</mat-icon>
</button>
<tb-color-picker-panel [color]="color"
colorCancelButton
[colorClearButton]="colorClearButton"
(colorCancelDialog)="cancel()"
(colorSelected)="selectColor($event)">
</tb-color-picker-panel>
</div>

28
ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.scss

@ -13,10 +13,38 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../scss/constants";
:host {
.tb-close-button {
position: absolute;
top: 6px;
right: 6px;
}
::ng-deep {
.mat-mdc-dialog-content {
max-height: 100%;
overflow: hidden;
padding: 12px 4px 12px 12px !important;
}
.tb-color-picker-panel {
@media #{$mat-sm} {
width: 342px;
.color-input-container {
flex-direction: column !important;
}
.color-presets-component color-preset {
width: 48px;
}
}
@media #{$mat-xs} {
width: 100%;
.hex-input {
flex: 1;
max-width: 100% !important;
}
}
}
}
}

3
ui-ngx/src/app/shared/components/popover.component.ts

@ -340,7 +340,7 @@ export class TbPopoverDirective implements OnChanges, OnDestroy, AfterViewInit {
<div class="tb-popover-inner" [ngStyle]="tbPopoverInnerStyle" role="tooltip">
<div *ngIf="tbShowCloseButton" class="tb-popover-close-button" (click)="closeButtonClick($event)">×</div>
<div style="width: 100%; height: 100%;">
<div class="tb-popover-inner-content"
<div class="tb-popover-inner-content" [ngStyle]="tbPopoverInnerContentStyle"
[class.strict-position]="strictPosition">
<ng-container *ngIf="tbContent">
<ng-container *tbStringTemplateOutlet="tbContent; context: tbComponentContext">
@ -378,6 +378,7 @@ export class TbPopoverComponent<T = any> implements OnDestroy, OnInit {
tbOverlayClassName!: string;
tbOverlayStyle: { [klass: string]: any } = {};
tbPopoverInnerStyle: { [klass: string]: any } = {};
tbPopoverInnerContentStyle: { [klass: string]: any } = {};
tbBackdrop = false;
tbMouseEnterDelay?: number;
tbMouseLeaveDelay?: number;

9
ui-ngx/src/app/shared/components/popover.service.ts

@ -71,17 +71,19 @@ export class TbPopoverService {
componentType: Type<T>, preferredPlacement: PopoverPreferredPlacement = 'top',
hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {},
popoverStyle: any = {}, style?: any,
showCloseButton = true, visibleFn: (visible: boolean) => void = () => {}): TbPopoverComponent<T> {
showCloseButton = true, visibleFn: (visible: boolean) => void = () => {},
popoverContentStyle: any = {}): TbPopoverComponent<T> {
const componentRef = this.createPopoverRef(hostView);
return this.displayPopoverWithComponentRef(componentRef, trigger, renderer, componentType, preferredPlacement, hideOnClickOutside,
injector, context, overlayStyle, popoverStyle, style, showCloseButton, visibleFn);
injector, context, overlayStyle, popoverStyle, style, showCloseButton, visibleFn, popoverContentStyle);
}
displayPopoverWithComponentRef<T>(componentRef: ComponentRef<TbPopoverComponent>, trigger: Element, renderer: Renderer2,
componentType: Type<T>, preferredPlacement: PopoverPreferredPlacement = 'top',
hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {},
popoverStyle: any = {}, style?: any, showCloseButton = true,
visibleFn: (visible: boolean) => void = () => {}): TbPopoverComponent<T> {
visibleFn: (visible: boolean) => void = () => {},
popoverContentStyle: any = {}): TbPopoverComponent<T> {
const component = componentRef.instance;
this.popoverWithTriggers.push({
trigger,
@ -99,6 +101,7 @@ export class TbPopoverService {
component.tbComponentContext = context;
component.tbOverlayStyle = overlayStyle;
component.tbPopoverInnerStyle = popoverStyle;
component.tbPopoverInnerContentStyle = popoverContentStyle;
component.tbComponentStyle = style;
component.tbHideOnClickOutside = hideOnClickOutside;
component.tbShowCloseButton = showCloseButton;

16
ui-ngx/src/app/shared/shared.module.ts

@ -178,15 +178,15 @@ import { TemplateAutocompleteComponent } from '@shared/components/notification/t
import { SlackConversationAutocompleteComponent } from '@shared/components/slack-conversation-autocomplete.component';
import { DateAgoPipe } from '@shared/pipe/date-ago.pipe';
import {
TbBreakPointsProvider,
MdLgLayoutDirective,
GtMdLgLayoutAlignDirective,
GtMdLgLayoutDirective,
GtMdLgLayoutGapDirective,
GtMdLgShowHideDirective,
MdLgLayoutAlignDirective,
MdLgLayoutDirective,
MdLgLayoutGapDirective,
MdLgShowHideDirective,
GtMdLgLayoutDirective,
GtMdLgLayoutAlignDirective,
GtMdLgLayoutGapDirective,
GtMdLgShowHideDirective
TbBreakPointsProvider
} from '@shared/layout/layout.directives';
import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component';
import { ResourceAutocompleteComponent } from '@shared/components/resource/resource-autocomplete.component';
@ -218,6 +218,7 @@ import { EmbedImageDialogComponent } from '@shared/components/image/embed-image-
import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component';
import { RuleChainSelectPanelComponent } from '@shared/components/rule-chain/rule-chain-select-panel.component';
import { WidgetButtonComponent } from '@shared/components/button/widget-button.component';
import { HexInputComponent } from '@shared/components/color-picker/hex-input.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -416,7 +417,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MultipleGalleryImageInputComponent,
EmbedImageDialogComponent,
ImageGalleryDialogComponent,
WidgetButtonComponent
WidgetButtonComponent,
HexInputComponent
],
imports: [
CommonModule,

Loading…
Cancel
Save