Skip to content

Commit ecc635f

Browse files
authored
feat: adding color picker component (#1325)
* feat: adding color picker component * refactor: downgrading version and fixing tests
1 parent 5456802 commit ecc635f

File tree

10 files changed

+245
-1
lines changed

10 files changed

+245
-1
lines changed

package-lock.json

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"iso8601-duration": "^1.3.0",
6565
"lodash-es": "^4.17.21",
6666
"mixpanel-browser": "^2.42.0",
67+
"ngx-color": "7.0.0",
6768
"rxjs": "~6.6.7",
6869
"tslib": "^2.3.1",
6970
"uuid": "^8.3.2",

projects/components/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"d3-axis": "^2.1.0",
2828
"d3-scale": "^3.3.0",
2929
"d3-selection": "^1.4.2",
30-
"d3-shape": "^1.3.5"
30+
"d3-shape": "^1.3.5",
31+
"ngx-color": "7.0.0"
3132
},
3233
"devDependencies": {
3334
"@hypertrace/test-utils": "^0.0.0"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@import 'color-palette';
2+
@import 'mixins';
3+
4+
.color-picker {
5+
display: flex;
6+
flex-flow: row wrap;
7+
align-items: center;
8+
9+
.color {
10+
width: 24px;
11+
height: 24px;
12+
border-radius: 50%;
13+
margin-right: 6px;
14+
cursor: pointer;
15+
16+
&.selected {
17+
border: 2px solid $blue-4;
18+
}
19+
}
20+
}
21+
22+
.container {
23+
width: 200px;
24+
height: 200px;
25+
cursor: pointer;
26+
}
27+
28+
.color-sketch {
29+
width: 200px;
30+
height: 200px;
31+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { CommonModule } from '@angular/common';
2+
import { fakeAsync } from '@angular/core/testing';
3+
import { IconType } from '@hypertrace/assets-library';
4+
import { Color, NavigationService } from '@hypertrace/common';
5+
import { IconComponent } from '@hypertrace/components';
6+
import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
7+
import { MockComponent } from 'ng-mocks';
8+
import { SketchComponent } from 'ngx-color/sketch';
9+
import { NEVER, Observable } from 'rxjs';
10+
import { NotificationService } from '../notification/notification.service';
11+
import { PopoverModule } from '../popover/popover.module';
12+
import { ColorPickerComponent } from './color-picker.component';
13+
14+
describe('Color Picker component', () => {
15+
let spectator: Spectator<ColorPickerComponent>;
16+
17+
const createHost = createHostFactory({
18+
component: ColorPickerComponent,
19+
imports: [CommonModule, PopoverModule],
20+
providers: [
21+
mockProvider(NotificationService, { withNotification: (x: Observable<unknown>) => x }),
22+
mockProvider(NavigationService, {
23+
navigation$: NEVER
24+
})
25+
],
26+
declarations: [MockComponent(SketchComponent), MockComponent(IconComponent)]
27+
});
28+
29+
test('should render color picker with default colors', fakeAsync(() => {
30+
const onSelectionChangeSpy = jest.fn();
31+
spectator = createHost(
32+
`<ht-color-picker (selectedChange)="onSelectionChange($event)">
33+
</ht-color-picker>`,
34+
{
35+
hostProps: {
36+
onSelectionChange: onSelectionChangeSpy
37+
}
38+
}
39+
);
40+
41+
const colors = spectator.queryAll('.color-picker .color');
42+
expect(colors.length).toBe(7);
43+
expect(spectator.query(IconComponent)?.icon).toBe(IconType.Add);
44+
45+
spectator.click(colors[1]);
46+
expect(spectator.component.selected).toEqual(Color.Blue3);
47+
expect(onSelectionChangeSpy).toHaveBeenCalledWith(Color.Blue3);
48+
49+
spectator.click('.add-icon');
50+
spectator.tick();
51+
52+
expect(spectator.query('.color-sketch', { root: true })).toExist();
53+
}));
54+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
3+
import { IconType } from '@hypertrace/assets-library';
4+
import { Color } from '@hypertrace/common';
5+
import { IconSize } from '../icon/icon-size';
6+
7+
@Component({
8+
selector: 'ht-color-picker',
9+
styleUrls: ['./color-picker.component.scss'],
10+
changeDetection: ChangeDetectionStrategy.OnPush,
11+
providers: [
12+
{
13+
provide: NG_VALUE_ACCESSOR,
14+
multi: true,
15+
useExisting: ColorPickerComponent
16+
}
17+
],
18+
template: `
19+
<div class="color-picker">
20+
<div
21+
class="color"
22+
*ngFor="let color of this.paletteColors"
23+
[ngClass]="{ selected: color === this.selected }"
24+
[style.backgroundColor]="color"
25+
(click)="this.selectColor(color)"
26+
></div>
27+
<ht-popover>
28+
<ht-popover-trigger>
29+
<ht-icon class="add-icon" icon="${IconType.Add}" size="${IconSize.Small}"></ht-icon>
30+
</ht-popover-trigger>
31+
<ht-popover-content>
32+
<div class="container">
33+
<color-sketch class="color-sketch" (onChange)="this.onAddColorToPalette($event.color.hex)"></color-sketch>
34+
</div>
35+
</ht-popover-content>
36+
</ht-popover>
37+
</div>
38+
`
39+
})
40+
export class ColorPickerComponent {
41+
@Input()
42+
public selected?: string;
43+
44+
@Output()
45+
private readonly selectedChange: EventEmitter<string> = new EventEmitter<string>();
46+
47+
private readonly paletteSet: Set<string> = new Set<string>([
48+
Color.Brown1,
49+
Color.Blue3,
50+
Color.Green3,
51+
Color.Orange3,
52+
Color.Purple3,
53+
Color.Red3,
54+
Color.Yellow3
55+
]);
56+
public paletteColors: string[] = Array.from(this.paletteSet);
57+
58+
private propagateControlValueChange?: (value: string | undefined) => void;
59+
private propagateControlValueChangeOnTouch?: (value: string | undefined) => void;
60+
61+
public onAddColorToPalette(color: string): void {
62+
this.paletteSet.add(color);
63+
this.paletteColors = Array.from(this.paletteSet);
64+
this.selectColor(color);
65+
}
66+
67+
public selectColor(color: string): void {
68+
this.selected = color;
69+
this.selectedChange.emit(color);
70+
this.propagateValueChangeToFormControl(color);
71+
}
72+
73+
private propagateValueChangeToFormControl(value?: string): void {
74+
this.propagateControlValueChange?.(value);
75+
this.propagateControlValueChangeOnTouch?.(value);
76+
}
77+
78+
public writeValue(color?: string): void {
79+
this.selected = color;
80+
}
81+
82+
public registerOnChange(onChange: (value?: string) => void): void {
83+
this.propagateControlValueChange = onChange;
84+
}
85+
86+
public registerOnTouched(onTouch: (value?: string) => void): void {
87+
this.propagateControlValueChangeOnTouch = onTouch;
88+
}
89+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { CommonModule } from '@angular/common';
2+
import { NgModule } from '@angular/core';
3+
import { FormsModule } from '@angular/forms';
4+
import { ColorSketchModule } from 'ngx-color/sketch';
5+
import { IconModule } from '../icon/icon.module';
6+
import { PopoverModule } from './../popover/popover.module';
7+
import { ColorPickerComponent } from './color-picker.component';
8+
9+
@NgModule({
10+
imports: [CommonModule, FormsModule, IconModule, ColorSketchModule, PopoverModule],
11+
declarations: [ColorPickerComponent],
12+
exports: [ColorPickerComponent]
13+
})
14+
export class ColorPickerModule {}

projects/components/src/form-field/form-field.component.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
.content {
3333
flex: 1 1 auto;
34+
display: flex;
3435
width: 100%;
3536
background: white;
3637

projects/components/src/public-api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export * from './tabs/content/tab-group.component';
3737
export * from './tabs/content/tab.module';
3838
export * from './tabs/content/tab/tab.component';
3939

40+
// Color picker
41+
export * from './color-picker/color-picker.component';
42+
export * from './color-picker/color-picker.module';
43+
4044
// Copy to Clipboard
4145
export * from './copy-to-clipboard/copy-to-clipboard.component';
4246
export * from './copy-to-clipboard/copy-to-clipboard.module';

tsconfig.base.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"noUnusedLocals": true,
1717
"noUnusedParameters": true,
1818
"downlevelIteration": true,
19+
"allowJs": true,
1920
"lib": ["es2015", "es2016", "es2017", "esnext.string", "esnext.array", "esnext.asynciterable", "dom"],
2021
"paths": {
2122
"@hypertrace/assets-library": ["projects/assets-library/src/public-api.ts"],

0 commit comments

Comments
 (0)