Skip to content

Commit 8439704

Browse files
authored
feat(module:color-picker): add color-picker component (#8013)
1 parent 15e244c commit 8439704

39 files changed

+1643
-2
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ components/image/** @stygian-desolator
7070
components/qr-code/** @OriginRing
7171
components/cron-expression/** @OriginRing
7272
components/water-mark/** @OriginRing
73+
components/color-picker/** @OriginRing
7374

7475
# The `components/core/*` owners
7576
components/core/config/** @hullis
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Component, DebugElement } from '@angular/core';
2+
import { ComponentFixture } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
4+
5+
import { NzColorBlockComponent, NzColorPickerModule } from 'ng-zorro-antd/color-picker';
6+
import { ComponentBed, createComponentBed } from 'ng-zorro-antd/core/testing/component-bed';
7+
import { NzSizeLDSType } from 'ng-zorro-antd/core/types';
8+
9+
describe('nz-color-block', () => {
10+
describe('basic', () => {
11+
let testBed: ComponentBed<NzTestColorBlockComponent>;
12+
let fixture: ComponentFixture<NzTestColorBlockComponent>;
13+
let testComponent: NzTestColorBlockComponent;
14+
let resultEl: DebugElement;
15+
16+
beforeEach(() => {
17+
testBed = createComponentBed(NzTestColorBlockComponent, {
18+
imports: [NzColorPickerModule]
19+
});
20+
fixture = testBed.fixture;
21+
fixture.detectChanges();
22+
testComponent = testBed.component;
23+
resultEl = fixture.debugElement.query(By.directive(NzColorBlockComponent));
24+
});
25+
26+
it('color-block basic', () => {
27+
fixture.detectChanges();
28+
const colorDom = resultEl.nativeElement.querySelector('.ant-color-picker-color-block-inner');
29+
expect(colorDom.style.backgroundColor).toBe('rgb(22, 119, 255)');
30+
});
31+
32+
it('color-block color', () => {
33+
testComponent.nzColor = '#ff6600';
34+
fixture.detectChanges();
35+
expect(resultEl.nativeElement.querySelector('.ant-color-picker-color-block-inner').style.backgroundColor).toBe(
36+
'rgb(255, 102, 0)'
37+
);
38+
});
39+
40+
it('color-block size', () => {
41+
testComponent.nzSize = 'small';
42+
fixture.detectChanges();
43+
expect(resultEl.nativeElement.querySelector('ng-antd-color-block').parentNode.classList).toContain(
44+
'ant-color-picker-inline-sm'
45+
);
46+
testComponent.nzSize = 'large';
47+
fixture.detectChanges();
48+
expect(resultEl.nativeElement.querySelector('ng-antd-color-block').parentNode.classList).toContain(
49+
'ant-color-picker-inline-lg'
50+
);
51+
});
52+
53+
it('color-block click', () => {
54+
fixture.detectChanges();
55+
resultEl.nativeElement.querySelector('.ant-color-picker-color-block').click();
56+
expect(testComponent.isClick).toBeTrue();
57+
});
58+
});
59+
});
60+
61+
@Component({
62+
template: `
63+
<nz-color-block [nzColor]="nzColor" [nzSize]="nzSize" (nzOnClick)="clickHandle($event)"></nz-color-block>
64+
`
65+
})
66+
export class NzTestColorBlockComponent {
67+
nzColor = '#1677ff';
68+
nzSize: NzSizeLDSType = 'default';
69+
isClick: boolean = false;
70+
71+
clickHandle(value: boolean): void {
72+
this.isClick = value;
73+
}
74+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Use of this source code is governed by an MIT-style license that can be
3+
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
4+
*/
5+
6+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
7+
8+
import { defaultColor } from 'ng-antd-color-picker';
9+
10+
import { NzSizeLDSType } from 'ng-zorro-antd/core/types';
11+
12+
@Component({
13+
selector: 'nz-color-block',
14+
exportAs: 'NzColorBlock',
15+
changeDetection: ChangeDetectionStrategy.OnPush,
16+
template: ` <ng-antd-color-block [color]="nzColor" (nzOnClick)="nzOnClick.emit($event)"></ng-antd-color-block> `,
17+
host: {
18+
class: 'ant-color-picker-inline',
19+
'[class.ant-color-picker-inline-sm]': `nzSize === 'small'`,
20+
'[class.ant-color-picker-inline-lg]': `nzSize === 'large'`
21+
}
22+
})
23+
export class NzColorBlockComponent {
24+
@Input() nzColor: string = defaultColor.toHexString();
25+
@Input() nzSize: NzSizeLDSType = 'default';
26+
@Output() readonly nzOnClick = new EventEmitter<boolean>();
27+
28+
constructor() {}
29+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/**
2+
* Use of this source code is governed by an MIT-style license that can be
3+
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
4+
*/
5+
6+
import {
7+
ChangeDetectionStrategy,
8+
SimpleChanges,
9+
Component,
10+
Input,
11+
OnChanges,
12+
OnDestroy,
13+
OnInit,
14+
Output,
15+
EventEmitter
16+
} from '@angular/core';
17+
import { AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
18+
import { Subject } from 'rxjs';
19+
import { debounceTime, filter, takeUntil } from 'rxjs/operators';
20+
21+
import { generateColor } from 'ng-antd-color-picker';
22+
23+
import { NzColorPickerFormatType } from './typings';
24+
25+
@Component({
26+
selector: 'nz-color-format',
27+
exportAs: 'NzColorFormat',
28+
changeDetection: ChangeDetectionStrategy.OnPush,
29+
template: `
30+
<div [formGroup]="validateForm" class="ant-color-picker-input-container">
31+
<div class="ant-color-picker-format-select">
32+
<nz-select formControlName="isFormat" nzBorderless nzSize="small">
33+
<nz-option nzValue="hex" nzLabel="HEX"></nz-option>
34+
<nz-option nzValue="hsb" nzLabel="HSB"></nz-option>
35+
<nz-option nzValue="rgb" nzLabel="RGB"></nz-option>
36+
</nz-select>
37+
</div>
38+
<div class="ant-color-picker-input" [ngSwitch]="validateForm.get('isFormat')?.value">
39+
<div class="ant-color-picker-hex-input" *ngSwitchCase="'hex'">
40+
<nz-input-group nzPrefix="#" nzSize="small">
41+
<input nz-input nzSize="small" formControlName="hex" />
42+
</nz-input-group>
43+
</div>
44+
45+
<div class="ant-color-picker-hsb-input" *ngSwitchCase="'hsb'">
46+
<div class="ant-color-picker-steppers ant-color-picker-hsb-input">
47+
<nz-input-number
48+
formControlName="hsbH"
49+
[nzMin]="0"
50+
[nzMax]="360"
51+
[nzStep]="1"
52+
[nzPrecision]="0"
53+
nzSize="small"
54+
></nz-input-number>
55+
</div>
56+
<div class="ant-color-picker-steppers ant-color-picker-hsb-input">
57+
<nz-input-number
58+
formControlName="hsbS"
59+
[nzMin]="0"
60+
[nzMax]="100"
61+
[nzStep]="1"
62+
[nzFormatter]="formatterPercent"
63+
[nzParser]="parserPercent"
64+
nzSize="small"
65+
></nz-input-number>
66+
</div>
67+
<div class="ant-color-picker-steppers ant-color-picker-hsb-input">
68+
<nz-input-number
69+
formControlName="hsbB"
70+
[nzMin]="0"
71+
[nzMax]="100"
72+
[nzStep]="1"
73+
[nzFormatter]="formatterPercent"
74+
[nzParser]="parserPercent"
75+
nzSize="small"
76+
></nz-input-number>
77+
</div>
78+
</div>
79+
80+
<div class="ant-color-picker-rgb-input" *ngSwitchDefault>
81+
<div class="ant-color-picker-steppers ant-color-picker-rgb-input">
82+
<nz-input-number formControlName="rgbR" [nzMin]="0" [nzMax]="255" [nzStep]="1" nzSize="small">
83+
</nz-input-number>
84+
</div>
85+
<div class="ant-color-picker-steppers ant-color-picker-rgb-input">
86+
<nz-input-number
87+
formControlName="rgbG"
88+
[nzMin]="0"
89+
[nzMax]="255"
90+
[nzStep]="1"
91+
nzSize="small"
92+
></nz-input-number>
93+
</div>
94+
<div class="ant-color-picker-steppers ant-color-picker-rgb-input">
95+
<nz-input-number
96+
formControlName="rgbB"
97+
[nzMin]="0"
98+
[nzMax]="255"
99+
[nzStep]="1"
100+
nzSize="small"
101+
></nz-input-number>
102+
</div>
103+
</div>
104+
</div>
105+
106+
<div class="ant-color-picker-steppers ant-color-picker-alpha-input">
107+
<nz-input-number
108+
formControlName="roundA"
109+
[nzMin]="0"
110+
[nzMax]="100"
111+
[nzStep]="1"
112+
[nzFormatter]="formatterPercent"
113+
[nzParser]="parserPercent"
114+
nzSize="small"
115+
></nz-input-number>
116+
</div>
117+
</div>
118+
`
119+
})
120+
export class NzColorFormatComponent implements OnChanges, OnInit, OnDestroy {
121+
@Input() format: NzColorPickerFormatType | null = null;
122+
@Input() colorValue: string = '';
123+
@Input() clearColor: boolean = false;
124+
@Output() readonly formatChange = new EventEmitter<{ color: string; format: NzColorPickerFormatType }>();
125+
@Output() readonly nzOnFormatChange = new EventEmitter<NzColorPickerFormatType>();
126+
127+
private destroy$ = new Subject<void>();
128+
129+
validatorFn(): ValidatorFn {
130+
return (control: AbstractControl): ValidationErrors | null => {
131+
const REGEXP = /^[0-9a-fA-F]{6}$/;
132+
if (!control.value) {
133+
return { error: true };
134+
} else if (!REGEXP.test(control.value)) {
135+
return { error: true };
136+
}
137+
return null;
138+
};
139+
}
140+
141+
validateForm: FormGroup<{
142+
isFormat: FormControl<NzColorPickerFormatType | null>;
143+
hex: FormControl<string | null>;
144+
hsbH: FormControl<number>;
145+
hsbS: FormControl<number>;
146+
hsbB: FormControl<number>;
147+
rgbR: FormControl<number>;
148+
rgbG: FormControl<number>;
149+
rgbB: FormControl<number>;
150+
roundA: FormControl<number>;
151+
}>;
152+
153+
formatterPercent = (value: number): string => `${value} %`;
154+
parserPercent = (value: string): string => value.replace(' %', '');
155+
156+
constructor(private formBuilder: FormBuilder) {
157+
this.validateForm = this.formBuilder.nonNullable.group({
158+
isFormat: this.formBuilder.control<NzColorPickerFormatType>('hex'),
159+
hex: this.formBuilder.control<string>('1677FF', this.validatorFn()),
160+
hsbH: 215,
161+
hsbS: 91,
162+
hsbB: 100,
163+
rgbR: 22,
164+
rgbG: 119,
165+
rgbB: 255,
166+
roundA: 100
167+
});
168+
}
169+
170+
ngOnInit(): void {
171+
this.validateForm.valueChanges
172+
.pipe(
173+
filter(() => this.validateForm.valid),
174+
debounceTime(200),
175+
takeUntil(this.destroy$)
176+
)
177+
.subscribe(value => {
178+
let color = '';
179+
switch (value.isFormat) {
180+
case 'hsb':
181+
color = generateColor({
182+
h: Number(value.hsbH),
183+
s: Number(value.hsbS) / 100,
184+
b: Number(value.hsbB) / 100,
185+
a: Number(value.roundA) / 100
186+
}).toHsbString();
187+
break;
188+
case 'rgb':
189+
color = generateColor({
190+
r: Number(value.rgbR),
191+
g: Number(value.rgbG),
192+
b: Number(value.rgbB),
193+
a: Number(value.roundA) / 100
194+
}).toRgbString();
195+
break;
196+
default:
197+
const hex = generateColor(value.hex as NzColorPickerFormatType);
198+
const hexColor = generateColor({
199+
r: hex.r,
200+
g: hex.g,
201+
b: hex.b,
202+
a: Number(value.roundA) / 100
203+
});
204+
color = hexColor.getAlpha() < 1 ? hexColor.toHex8String() : hexColor.toHexString();
205+
break;
206+
}
207+
this.formatChange.emit({ color, format: value.isFormat || this.format || 'hex' });
208+
});
209+
210+
this.validateForm
211+
.get('isFormat')
212+
?.valueChanges.pipe(takeUntil(this.destroy$))
213+
.subscribe(value => {
214+
this.nzOnFormatChange.emit(value as NzColorPickerFormatType);
215+
});
216+
}
217+
218+
ngOnChanges(changes: SimpleChanges): void {
219+
const { colorValue, format, clearColor } = changes;
220+
if (colorValue) {
221+
const colorValue = {
222+
hex: generateColor(this.colorValue).toHex(),
223+
hsbH: Math.round(generateColor(this.colorValue).toHsb().h),
224+
hsbS: Math.round(generateColor(this.colorValue).toHsb().s * 100),
225+
hsbB: Math.round(generateColor(this.colorValue).toHsb().b * 100),
226+
rgbR: Math.round(generateColor(this.colorValue).r),
227+
rgbG: Math.round(generateColor(this.colorValue).g),
228+
rgbB: Math.round(generateColor(this.colorValue).b),
229+
roundA: Math.round(generateColor(this.colorValue).roundA * 100)
230+
};
231+
this.validateForm.patchValue(colorValue);
232+
}
233+
234+
if (format && this.format) {
235+
this.validateForm.get('isFormat')?.patchValue(this.format);
236+
}
237+
238+
if (clearColor && this.clearColor) {
239+
this.validateForm.get('roundA')?.patchValue(0);
240+
}
241+
}
242+
243+
ngOnDestroy(): void {
244+
this.destroy$.next();
245+
this.destroy$.complete();
246+
}
247+
}

0 commit comments

Comments
 (0)