Skip to content

Commit bef0dc8

Browse files
committed
feat: adding color picker component
1 parent 5456802 commit bef0dc8

File tree

9 files changed

+228
-1
lines changed

9 files changed

+228
-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.3.3",
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.3.3"
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { IconType } from '@hypertrace/assets-library';
2+
import { IconComponent } from '@hypertrace/components';
3+
import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
4+
import { ColorPickerComponent } from './color-picker.component';
5+
import { SketchComponent } from 'ngx-color/sketch';
6+
import { PopoverModule } from '../popover/popover.module';
7+
import { NotificationService } from '../notification/notification.service';
8+
import { CommonModule } from '@angular/common';
9+
import { Observable } from 'rxjs';
10+
import { MockComponent } from 'ng-mocks';
11+
12+
describe('Color Picker component', () => {
13+
let spectator: Spectator<ColorPickerComponent>;
14+
15+
const createHost = createHostFactory({
16+
component: ColorPickerComponent,
17+
imports: [CommonModule, PopoverModule],
18+
providers: [mockProvider(NotificationService, { withNotification: (x: Observable<unknown>) => x })],
19+
declareComponent: false,
20+
declarations: [MockComponent(SketchComponent), MockComponent(IconComponent)]
21+
});
22+
23+
test('should render color picker with default colors', () => {
24+
const onSelectionChangeSpy = jest.fn();
25+
spectator = createHost(
26+
`<ht-color-picker (selectedChange)="onSelectionChange($event)">
27+
</ht-color-picker>`,
28+
{
29+
hostProps: {
30+
onSelectionChange: onSelectionChangeSpy
31+
}
32+
}
33+
);
34+
35+
expect(spectator.queryAll('.color-picker .color').length).toBe(7);
36+
expect(spectator.query(IconComponent)?.icon).toBe(IconType.Add);
37+
});
38+
});
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 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';

0 commit comments

Comments
 (0)