Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components/forms): form error new pattern #1955

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a5fbbf9
add label text input
Blackbaud-ErikaMcVey Jan 10, 2024
987412a
Merge branch 'main' into colorpicker-easy-mode
Blackbaud-ErikaMcVey Jan 10, 2024
a9fed3c
checkbox fixes
Blackbaud-ErikaMcVey Jan 11, 2024
169dd47
add form errors to colorpicker
Blackbaud-ErikaMcVey Jan 11, 2024
2e83033
form erorrs token, form error errorType, misc
Blackbaud-ErikaMcVey Jan 12, 2024
237f38f
Merge branch 'main' into colorpicker-easy-mode
Blackbaud-ErikaMcVey Jan 12, 2024
e02a191
add checkbox stuff back
Blackbaud-ErikaMcVey Jan 12, 2024
0a3b5b3
formatting fixes
Blackbaud-ErikaMcVey Jan 12, 2024
b549a74
colorpicker error tests, clean up
Blackbaud-ErikaMcVey Jan 17, 2024
34fabe4
input custom error ngcontent and form error harness
Blackbaud-SandhyaRajasabeson Jan 17, 2024
936d7a2
input box test harness
Blackbaud-SandhyaRajasabeson Jan 19, 2024
79ad69e
checkbox harness updates
Blackbaud-ErikaMcVey Jan 22, 2024
850c071
colorpicker code example
Blackbaud-ErikaMcVey Jan 22, 2024
d51a02f
formatting
Blackbaud-ErikaMcVey Jan 22, 2024
0899ea1
remove character count and unit tests up till timepicker
Blackbaud-SandhyaRajasabeson Jan 22, 2024
25bfc15
directive unit tests
Blackbaud-SandhyaRajasabeson Jan 23, 2024
1a39bd6
Merge branch '9.x.x' into colorpicker-easy-mode
Blackbaud-ErikaMcVey Jan 24, 2024
9684956
inmput box demo unit test
Blackbaud-SandhyaRajasabeson Jan 24, 2024
3cc32a1
checkbox ngcontrol non protected
Blackbaud-SandhyaRajasabeson Jan 24, 2024
073e465
clean up, add test
Blackbaud-ErikaMcVey Jan 24, 2024
8c0a4fd
cleanup
Blackbaud-SandhyaRajasabeson Jan 24, 2024
c0d0a68
documentation
Blackbaud-SandhyaRajasabeson Jan 24, 2024
1e2b68e
pull colorpicker input updates into service
Blackbaud-ErikaMcVey Jan 26, 2024
fc228b9
Merge branch '9.x.x' into colorpicker-easy-mode
Blackbaud-ErikaMcVey Jan 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
[skyColorpickerInput]="colorPicker"
#colorInput="skyId"
/>
<sky-form-error
*ngIf="favoriteColor.errors?.['opaque']"
errorName="opaque"
>
Color must have at least 80% opacity.
</sky-form-error>
</sky-colorpicker>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
UntypedFormControl,
ValidationErrors,
} from '@angular/forms';
import { SkyColorpickerModule, SkyColorpickerOutput } from '@skyux/colorpicker';
import { SkyIdModule } from '@skyux/core';
Expand All @@ -12,10 +15,16 @@ import { SkyIdModule } from '@skyux/core';
standalone: true,
selector: 'app-demo',
templateUrl: './demo.component.html',
imports: [ReactiveFormsModule, SkyColorpickerModule, SkyIdModule],
imports: [
CommonModule,
ReactiveFormsModule,
SkyColorpickerModule,
SkyIdModule,
],
})
export class DemoComponent {
protected formGroup: FormGroup;
protected favoriteColor: UntypedFormControl;

protected swatches: string[] = [
'#BD4040',
Expand All @@ -27,8 +36,18 @@ export class DemoComponent {
];

constructor() {
this.favoriteColor = new UntypedFormControl('#f00', [
(control: AbstractControl): ValidationErrors | null => {
if (control.value?.rgba?.alpha < 0.8) {
return { opaque: true };
}

return null;
},
]);

this.formGroup = inject(FormBuilder).group({
favoriteColor: new FormControl('#f00'),
favoriteColor: this.favoriteColor,
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
<label class="sky-control-label"> Contact method </label>
<div class="sky-margin-stacked-xl">
<ul class="sky-list-unstyled">
<li>
<sky-checkbox formControlName="email">
<sky-checkbox-label> Email </sky-checkbox-label>
</sky-checkbox>
<sky-checkbox formControlName="email" labelText="Email" />
</li>
<li>
<sky-checkbox formControlName="phone">
<sky-checkbox-label> Phone </sky-checkbox-label>
</sky-checkbox>
<sky-checkbox formControlName="phone" labelText="Phone" />
</li>
<li>
<sky-checkbox formControlName="text">
<sky-checkbox-label> Text </sky-checkbox-label>
</sky-checkbox>
<sky-checkbox formControlName="text" labelText="Text" />
</li>
</ul>
</div>
<div>
<sky-checkbox
formControlName="terms"
[required]="true"
labelText="I agree to the terms and conditions"
/>
</div>
<button class="sky-btn sky-btn-primary" type="submit">Submit</button>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ export class DemoComponent {
email: new FormControl(false),
phone: new FormControl(false),
text: new FormControl(false),
terms: new FormControl(false),
});
}

protected onSubmit(): void {
this.formGroup.markAllAsTouched();

console.log(this.formGroup.value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@ <h2>New member form</h2>
</sky-input-box>
</sky-column>
<sky-column [screenSmall]="12" [screenMedium]="6">
<sky-input-box labelText="Last name" stacked="true">
<input formControlName="lastName" spellcheck="false" type="text" />
<sky-input-box
data-sky-id="input-box-last-name"
labelText="Last name"
stacked="true"
>
<input
class="last-name-input-box"
formControlName="lastName"
spellcheck="false"
type="text"
/>
</sky-input-box>
</sky-column>
</sky-row>
Expand Down Expand Up @@ -69,18 +78,15 @@ <h2>New member form</h2>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="purple">Purple</option>
<option value="bird">Bird</option>
<option value="blur">Blur</option>
</select>
<!-- Custom validator not handled by input box. -->
<div class="sky-error-indicator">
<sky-status-indicator
*ngIf="favoriteColor.errors?.['color'] as colorError"
descriptionType="error"
indicatorType="danger"
>
{{ colorError.message }}
</sky-status-indicator>
</div>
<!-- Custom form error not handled by input box. -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just remove the custom error above this one--we'll probably get rid of that way eventually, and all other components will just use sky-form-error

<sky-form-error
*ngIf="favoriteColor.errors?.['color']"
errorName="color"
>
Blur is not a color.
</sky-form-error>
</sky-input-box>
</sky-column>
</sky-row>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { SkyAppTestUtility } from '@skyux-sdk/testing';
import { SkyInputBoxHarness } from '@skyux/forms/testing';

import { DemoComponent } from './demo.component';
Expand Down Expand Up @@ -40,6 +41,22 @@ describe('Basic input box demo', () => {
});
});

describe('last name field', () => {
it('should have last name required', async () => {
const harness = await setupTest({
dataSkyId: 'input-box-last-name',
});
const inputEl = document.querySelector(
'input.last-name-input-box',
) as HTMLInputElement;
inputEl.value = '';
SkyAppTestUtility.fireDomEvent(inputEl, 'input');
SkyAppTestUtility.fireDomEvent(inputEl, 'blur');

await expectAsync(harness.hasRequiredError()).toBeResolvedTo(true);
});
});

describe('bio field', () => {
it('should have a character limit of 250', async () => {
const harness = await setupTest({
Expand Down Expand Up @@ -71,7 +88,7 @@ describe('Basic input box demo', () => {
});

describe('favorite color field', () => {
it('should not allow bird to be selected', async () => {
it('should not allow blur to be selected', async () => {
const harness = await setupTest({
dataSkyId: 'input-box-favorite-color',
});
Expand All @@ -80,19 +97,11 @@ describe('Basic input box demo', () => {
'.input-box-favorite-color-select',
) as HTMLSelectElement;

selectEl.value = 'bird';
selectEl.value = 'blur';
selectEl.dispatchEvent(new Event('change'));

const customErrors = await harness.getCustomErrors();

expect(customErrors.length).toBe(1);

const birdError = customErrors[0];

await expectAsync(birdError.getDescriptionType()).toBeResolvedTo('error');
await expectAsync(birdError.getIndicatorType()).toBeResolvedTo('danger');
await expectAsync(birdError.getText()).toBeResolvedTo(
'Bird is not a color.',
await expectAsync(harness.hasCustomFormError('color')).toBeResolvedTo(
true,
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,8 @@ export class DemoComponent {
constructor() {
this.favoriteColor = new FormControl('none', [
(control): ValidationErrors | null => {
if (control.value === 'bird') {
return {
color: {
invalid: true,
message: 'Bird is not a color.',
},
};
if (control.value === 'blur') {
return { color: true };
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
<sky-modal data-sky-id="modal-demo" [isDirty]="demoForm.dirty">
<sky-modal-header> Modal title </sky-modal-header>
<sky-modal-content>
<sky-input-box labelText="An input inside a modal">
<sky-input-box labelText="An input inside a modal" [stacked]="true">
<input formControlName="value1" type="text" />
</sky-input-box>
<sky-checkbox formControlName="checkbox" [required]="true">
<sky-checkbox-label>
I agree to terms and conditions
</sky-checkbox-label>
</sky-checkbox>
</sky-modal-content>
<sky-modal-footer>
<button class="sky-btn sky-btn-primary" type="submit">Save</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, inject } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { SkyInputBoxModule } from '@skyux/forms';
import { SkyCheckboxModule, SkyInputBoxModule } from '@skyux/forms';
import { SkyWaitService } from '@skyux/indicators';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';

Expand All @@ -11,11 +11,17 @@ import { ModalDemoDataService } from './data.service';
standalone: true,
selector: 'app-modal',
templateUrl: './modal.component.html',
imports: [ReactiveFormsModule, SkyInputBoxModule, SkyModalModule],
imports: [
ReactiveFormsModule,
SkyInputBoxModule,
SkyModalModule,
SkyCheckboxModule,
],
})
export class ModalComponent {
protected demoForm: FormGroup<{
value1: FormControl<string | null | undefined>;
checkbox: FormControl<boolean | null>;
}>;

readonly #context = inject(ModalDemoContext);
Expand All @@ -26,18 +32,23 @@ export class ModalComponent {
constructor() {
this.demoForm = new FormGroup({
value1: new FormControl(this.#context.data.value1),
checkbox: new FormControl(false),
});
}

protected saveForm(): void {
// Use the data service to save the data.
// Mark all fields as touched to ensure validation has run
this.demoForm.markAllAsTouched();

this.#waitSvc
.blockingWrap(this.#dataSvc.save(this.demoForm.value))
.subscribe((data) => {
// Notify the modal instance that data was saved and return the saved data.
this.#instance.save(data);
});
// Use the data service to save the data.
if (this.demoForm.valid) {
this.#waitSvc
.blockingWrap(this.#dataSvc.save(this.demoForm.value))
.subscribe((data) => {
// Notify the modal instance that data was saved and return the saved data.
this.#instance.save(data);
});
}
}

protected cancelForm(): void {
Expand Down
2 changes: 2 additions & 0 deletions apps/e2e/forms-storybook-e2e/src/e2e/checkbox.component.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ describe('forms-storybook - checkbox', () => {
cy.get('app-checkbox')
.should('exist')
.should('be.visible')
.get('#touched-required-checkbox')
.dblclick()
.get('#standard-checkboxes')
.should('exist')
.should('be.visible')
Expand Down
11 changes: 11 additions & 0 deletions apps/e2e/forms-storybook/src/app/checkbox/checkbox.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
</sky-checkbox>
</sky-column>
</sky-row>
<sky-row class="sky-padding-even-md">
<sky-column>
<sky-checkbox
ngModel
[checked]="false"
[required]="true"
labelText="Required Checkbox"
id="touched-required-checkbox"
/>
</sky-column>
</sky-row>
<sky-row class="sky-padding-even-md">
<sky-column>
<sky-checkbox [checked]="false" [disabled]="true">
Expand Down
2 changes: 2 additions & 0 deletions apps/e2e/forms-storybook/src/app/checkbox/checkbox.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyHelpInlineModule } from '@skyux/indicators';
Expand All @@ -16,6 +17,7 @@ const routes: Routes = [{ path: '', component: CheckboxComponent }];
SkyCheckboxModule,
SkyFluidGridModule,
SkyHelpInlineModule,
FormsModule,
RouterModule.forChild(routes),
],
exports: [CheckboxComponent],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
</ng-container>
<ng-container *ngSwitchCase="'Color'">
<div class="sky-form-group">
<label class="sky-control-label" [for]="input.id">{{
<!-- <label class="sky-control-label" [for]="input.id">{{
field
}}</label>
<sky-colorpicker #colorPicker>
}}</label> -->
<!-- <sky-colorpicker #colorPicker>
<input
[formControlName]="field"
[skyColorpickerInput]="colorPicker"
skyId
#input="skyId"
type="text"
/>
</sky-colorpicker>
</sky-colorpicker> -->
</div>
</ng-container>
</ng-container>
Expand Down
Loading
Loading