(FuseMaterialColorPicker) Greatly simplified the color picker, added reactive forms support and improved its design, closes #79

This commit is contained in:
Sercan Yemen 2018-07-26 14:10:35 +03:00
parent bbd59ab6c3
commit 0f1048cb3c
5 changed files with 196 additions and 217 deletions

View File

@ -1,20 +1,20 @@
<button mat-icon-button <button mat-icon-button
type="button"
class="mat-elevation-z1" class="mat-elevation-z1"
[matMenuTriggerFor]="colorMenu" [matMenuTriggerFor]="colorMenu"
(menuOpened)="onMenuOpen()" (menuOpened)="onMenuOpen()"
[ngClass]="'mat-'+selectedPalette+'-'+selectedHue+'-bg'"> [ngClass]="selectedPalette + '-' + selectedHue">
<mat-icon>palette</mat-icon> <mat-icon>palette</mat-icon>
</button> </button>
<mat-menu #colorMenu="matMenu" class="fuse-material-color-picker-menu"> <mat-menu #colorMenu="matMenu" class="fuse-material-color-picker-menu mat-elevation-z8">
<header [ngClass]="selectedColor?.class || 'mat-accent-bg'" class="mat-elevation-z4" <header [ngClass]="selectedColor?.class || 'accent'" class="mat-elevation-z4"
fxLayout="row" fxLayoutAlign="space-between center"> fxLayout="row" fxLayoutAlign="space-between center">
<button mat-icon-button <button mat-icon-button
class="secondary-text"
[style.visibility]="view === 'hues' ? 'visible' : 'hidden'" [style.visibility]="view === 'hues' ? 'visible' : 'hidden'"
(click)="$event.stopPropagation();backToPaletteSelection()" aria-label="Palette"> (click)="goToPalettesView($event)" aria-label="Palette">
<mat-icon class="s-20">arrow_back</mat-icon> <mat-icon class="s-20">arrow_back</mat-icon>
</button> </button>
@ -23,45 +23,40 @@
</span> </span>
<span *ngIf="!selectedColor?.palette"> <span *ngIf="!selectedColor?.palette">
Select Color Select a Color
</span> </span>
<button mat-icon-button <button mat-icon-button
class="remove-color-button" class="remove-color-button secondary-text"
(click)="$event.stopPropagation();removeColor()" (click)="removeColor($event)"
aria-label="Remove Color"> aria-label="Remove color"
matTooltip="Remove color">
<mat-icon class="s-20">delete</mat-icon> <mat-icon class="s-20">delete</mat-icon>
</button> </button>
</header> </header>
<div [ngSwitch]="view" class="views"> <div [ngSwitch]="view" class="views">
<div class="view" *ngSwitchCase="'palettes'"> <div class="view" *ngSwitchCase="'palettes'">
<div fxLayout="row wrap" fxLayoutAlign="start start" class="colors" fusePerfectScrollbar> <div fxLayout="row wrap" fxLayoutAlign="start start" class="colors" fusePerfectScrollbar>
<div class="color" <div class="color" fxLayout="row" fxLayoutAlign="center center"
[ngClass]="'mat-'+color.key+'-bg'"
*ngFor="let color of (colors | keys)" *ngFor="let color of (colors | keys)"
(click)="$event.stopPropagation();selectPalette(color.key)" [ngClass]="color.key"
fxLayout="row" fxLayoutAlign="start end" mat-ink-ripple> [class.selected]="selectedPalette === color.key"
<span class="label"> (click)="selectPalette($event, color.key)">
{{color.key}}
</span>
</div> </div>
</div> </div>
</div> </div>
<div class="view" *ngSwitchCase="'hues'"> <div class="view" *ngSwitchCase="'hues'">
<div fxLayout="row wrap" fxLayoutAlign="start start" class="colors" fusePerfectScrollbar> <div fxLayout="row wrap" fxLayoutAlign="start start" class="colors" fusePerfectScrollbar>
<div class="color" *ngFor="let hue of hues" <div class="color" fxLayout="row" fxLayoutAlign="center center"
[fxHide]="selectedPalette === 'white' && hue !== '500'|| selectedPalette === 'black' && hue !== '500'" *ngFor="let hue of hues"
[ngClass]="'mat-'+selectedPalette+'-'+hue+'-bg'" [fxHide]="selectedPalette === 'fuse-white' && hue !== '500' || selectedPalette === 'fuse-black' && hue !== '500'"
(click)="selectHue(hue)" fxLayout="row" fxLayoutAlign="start end" mat-ink-ripple> [ngClass]="selectedPalette + '-' + hue"
<span class="label"> [class.selected]="selectedHue === hue"
{{hue}} (click)="selectHue($event, hue)">
</span>
<mat-icon *ngIf="selectedHue === hue" class="s-16">check</mat-icon>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
.fuse-material-color-picker-menu { .fuse-material-color-picker-menu {
width: 208px; width: 245px;
.mat-menu-content { .mat-menu-content {
padding: 0; padding: 0;
@ -7,44 +7,29 @@
.views { .views {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; min-height: 165px;
overflow: hidden;
min-height: 258px;
height: 308px;
background-color: #F7F7F7;
.view { .view {
position: absolute; overflow: hidden;
width: 208px;
height: 100%;
bottom: 0;
left: 0;
right: 0;
top: 0;
.colors { .colors {
position: relative; padding: 1px 0 0 0;
padding: 4px; margin-left: -1px;
.color { .color {
position: relative; width: 40px;
width: 46px; height: 40px;
height: 46px; margin: 0 0 1px 1px;
margin: 2px;
border-radius: 0; border-radius: 0;
cursor: pointer; cursor: pointer;
transition: border-radius .4s cubic-bezier(.25, .8, .25, 1);
.label { &:hover {
padding: 2px; border-radius: 20%;
font-size: 10px;
} }
mat-icon { &.selected {
position: absolute; border-radius: 50% !important;
top: 2px;
right: 2px;
font-size: 16px;
opacity: 0.7;
} }
} }
} }

View File

@ -1,55 +1,40 @@
import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, forwardRef, Input, Output, ViewEncapsulation } from '@angular/core';
import { fuseAnimations } from '@fuse/animations'; import { fuseAnimations } from '@fuse/animations';
import { MatColors } from '@fuse/mat-colors'; import { MatColors } from '@fuse/mat-colors';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
export const FUSE_MATERIAL_COLOR_PICKER_VALUE_ACCESSOR: any = {
provide : NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FuseMaterialColorPickerComponent),
multi : true
};
@Component({ @Component({
selector : 'fuse-material-color-picker', selector : 'fuse-material-color-picker',
templateUrl : './material-color-picker.component.html', templateUrl : './material-color-picker.component.html',
styleUrls : ['./material-color-picker.component.scss'], styleUrls : ['./material-color-picker.component.scss'],
animations : fuseAnimations, animations : fuseAnimations,
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None,
providers : [FUSE_MATERIAL_COLOR_PICKER_VALUE_ACCESSOR]
}) })
export class FuseMaterialColorPickerComponent implements OnChanges export class FuseMaterialColorPickerComponent implements ControlValueAccessor
{ {
colors: any; colors: any;
hues: string[]; hues: string[];
selectedColor: any;
view: string; view: string;
selectedColor: any;
@Input()
selectedPalette: string; selectedPalette: string;
@Input()
selectedHue: string; selectedHue: string;
@Input() // Color changed
selectedFg: string;
@Input()
value: any;
@Output() @Output()
onValueChange: EventEmitter<any>; colorChanged: EventEmitter<any>;
@Output()
selectedPaletteChange: EventEmitter<any>;
@Output()
selectedHueChange: EventEmitter<any>;
@Output()
selectedClassChange: EventEmitter<any>;
@Output()
selectedBgChange: EventEmitter<any>;
@Output()
selectedFgChange: EventEmitter<any>;
// Private // Private
_selectedClass: string; private _color: string;
_selectedBg: string; private _modelChange: (value: any) => void;
private _modelTouched: (value: any) => void;
/** /**
* Constructor * Constructor
@ -57,23 +42,18 @@ export class FuseMaterialColorPickerComponent implements OnChanges
constructor() constructor()
{ {
// Set the defaults // Set the defaults
this.colorChanged = new EventEmitter();
this.colors = MatColors.all; this.colors = MatColors.all;
this.hues = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', 'A100', 'A200', 'A400', 'A700']; this.hues = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', 'A100', 'A200', 'A400', 'A700'];
this.selectedFg = ''; this.selectedHue = '500';
this.selectedHue = '';
this.selectedPalette = '';
this.view = 'palettes'; this.view = 'palettes';
this.onValueChange = new EventEmitter();
this.selectedPaletteChange = new EventEmitter();
this.selectedHueChange = new EventEmitter();
this.selectedClassChange = new EventEmitter();
this.selectedBgChange = new EventEmitter();
this.selectedFgChange = new EventEmitter();
// Set the private defaults // Set the private defaults
this._selectedClass = ''; this._color = '';
this._selectedBg = ''; this._modelChange = () => {
};
this._modelTouched = () => {
};
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -86,88 +66,76 @@ export class FuseMaterialColorPickerComponent implements OnChanges
* @param value * @param value
*/ */
@Input() @Input()
set selectedClass(value) set color(value)
{ {
if ( value && value !== '' && this._selectedClass !== value ) if ( !value || value === '' || this._color === value )
{ {
const color = value.split('-');
if ( color.length >= 5 )
{
this.selectedPalette = color[1] + '-' + color[2];
this.selectedHue = color[3];
}
else
{
this.selectedPalette = color[1];
this.selectedHue = color[2];
}
}
this._selectedClass = value;
}
get selectedClass(): string
{
return this._selectedClass;
}
/**
* Selected bg
*
* @param value
*/
@Input()
set selectedBg(value)
{
if ( value && value !== '' && this._selectedBg !== value )
{
for ( const palette in this.colors )
{
if ( !this.colors.hasOwnProperty(palette) )
{
continue;
}
for ( const hue of this.hues )
{
if ( this.colors[palette][hue] === value )
{
this.selectedPalette = palette;
this.selectedHue = hue;
break;
}
}
}
}
this._selectedBg = value;
}
get selectedBg(): string
{
return this._selectedBg;
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On changes
*
* @param changes
*/
ngOnChanges(changes: any): void
{
if ( changes.selectedBg && changes.selectedBg.currentValue === '' ||
changes.selectedClass && changes.selectedClass.currentValue === '' ||
changes.selectedPalette && changes.selectedPalette.currentValue === '' )
{
this.removeColor();
return; return;
} }
if ( changes.selectedPalette || changes.selectedHue || changes.selectedClass || changes.selectedBg )
{ // Split the color value (red-400, blue-500, fuse-navy-700 etc.)
this.updateSelectedColor(); const colorParts = value.split('-');
// Take the very last part as the selected hue value
this.selectedHue = colorParts[colorParts.length - 1];
// Remove the last part
colorParts.pop();
// Rejoin the remaining parts as the selected palette name
this.selectedPalette = colorParts.join('-');
// Store the color value
this._color = value;
} }
get color(): string
{
return this._color;
}
// -----------------------------------------------------------------------------------------------------
// @ Control Value Accessor implementation
// -----------------------------------------------------------------------------------------------------
/**
* Register on change function
*
* @param fn
*/
registerOnChange(fn: any): void
{
this._modelChange = fn;
}
/**
* Register on touched function
*
* @param fn
*/
registerOnTouched(fn: any): void
{
this._modelTouched = fn;
}
/**
* Write value to the view from model
*
* @param color
*/
writeValue(color: any): void
{
// Return if null
if ( !color )
{
return;
}
// Set the color
this.color = color;
// Update the selected color
this.updateSelectedColor();
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -177,35 +145,61 @@ export class FuseMaterialColorPickerComponent implements OnChanges
/** /**
* Select palette * Select palette
* *
* @param event
* @param palette * @param palette
*/ */
selectPalette(palette): void selectPalette(event, palette): void
{ {
this.selectedPalette = palette; // Stop propagation
this.updateSelectedColor(); event.stopPropagation();
// Go to 'hues' view
this.view = 'hues'; this.view = 'hues';
// Update the selected palette
this.selectedPalette = palette;
// Update the selected color
this.updateSelectedColor();
} }
/** /**
* Select hue * Select hue
* *
* @param event
* @param hue * @param hue
*/ */
selectHue(hue): void selectHue(event, hue): void
{ {
// Stop propagation
event.stopPropagation();
// Update the selected huse
this.selectedHue = hue; this.selectedHue = hue;
// Update the selected color
this.updateSelectedColor(); this.updateSelectedColor();
} }
/** /**
* Remove color * Remove color
*
* @param event
*/ */
removeColor(): void removeColor(event): void
{ {
// Stop propagation
event.stopPropagation();
// Return to the 'palettes' view
this.view = 'palettes';
// Clear the selected palette and hue
this.selectedPalette = ''; this.selectedPalette = '';
this.selectedHue = ''; this.selectedHue = '';
// Update the selected color
this.updateSelectedColor(); this.updateSelectedColor();
this.view = 'palettes';
} }
/** /**
@ -213,49 +207,40 @@ export class FuseMaterialColorPickerComponent implements OnChanges
*/ */
updateSelectedColor(): void updateSelectedColor(): void
{ {
setTimeout(() => { if ( this.selectedColor && this.selectedColor.palette === this.selectedPalette && this.selectedColor.hue === this.selectedHue )
if ( this.selectedColor && this.selectedPalette === this.selectedColor.palette && this.selectedHue === this.selectedColor.hue )
{ {
return; return;
} }
if ( this.selectedPalette !== '' && this.selectedHue !== '' ) // Set the selected color object
{
this.selectedBg = MatColors.getColor(this.selectedPalette)[this.selectedHue];
this.selectedFg = MatColors.getColor(this.selectedPalette).contrast[this.selectedHue];
this.selectedClass = 'mat-' + this.selectedPalette + '-' + this.selectedHue + '-bg';
}
else
{
this.selectedBg = '';
this.selectedFg = '';
}
this.selectedColor = { this.selectedColor = {
palette: this.selectedPalette, palette: this.selectedPalette,
hue : this.selectedHue, hue : this.selectedHue,
class : this.selectedClass, class : this.selectedPalette + '-' + this.selectedHue,
bg : this.selectedBg, bg : this.selectedPalette === '' ? '' : MatColors.getColor(this.selectedPalette)[this.selectedHue],
fg : this.selectedFg fg : this.selectedPalette === '' ? '' : MatColors.getColor(this.selectedPalette).contrast[this.selectedHue]
}; };
this.selectedPaletteChange.emit(this.selectedPalette); // Emit the color changed event
this.selectedHueChange.emit(this.selectedHue); this.colorChanged.emit(this.selectedColor);
this.selectedClassChange.emit(this.selectedClass);
this.selectedBgChange.emit(this.selectedBg);
this.selectedFgChange.emit(this.selectedFg);
this.value = this.selectedColor; // Mark the model as touched
this.onValueChange.emit(this.selectedColor); this._modelTouched(this.selectedColor.class);
});
// Update the model
this._modelChange(this.selectedColor.class);
} }
/** /**
* Go back to palette selection * Go to palettes view
*
* @param event
*/ */
backToPaletteSelection(): void goToPalettesView(event): void
{ {
// Stop propagation
event.stopPropagation();
this.view = 'palettes'; this.view = 'palettes';
} }

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule, MatIconModule, MatMenuModule, MatRippleModule } from '@angular/material'; import { MatButtonModule, MatIconModule, MatMenuModule, MatTooltipModule } from '@angular/material';
import { FusePipesModule } from '@fuse/pipes/pipes.module'; import { FusePipesModule } from '@fuse/pipes/pipes.module';
@ -19,7 +19,7 @@ import { FuseMaterialColorPickerComponent } from '@fuse/components/material-colo
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatMenuModule, MatMenuModule,
MatRippleModule, MatTooltipModule,
FusePipesModule FusePipesModule
], ],

View File

@ -0,0 +1,14 @@
@mixin fuse-material-color-picker-theme($theme) {
$background: map_get($theme, background);
.fuse-material-color-picker-menu {
.mat-menu-content {
.views {
background: #303030;
}
}
}
}