-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation of the form input component from the new component library. I have kept the old implementation and added a way to also use it without a form control, also using a debounce + style changes
- Loading branch information
1 parent
e975abe
commit 9c92e7c
Showing
39 changed files
with
634 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* @format */ | ||
import { NgModule } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { | ||
FaIconLibrary, | ||
FontAwesomeModule, | ||
} from '@fortawesome/angular-fontawesome'; | ||
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; | ||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
import { FormInputComponent } from './components/form-input/form-input.component'; | ||
|
||
@NgModule({ | ||
declarations: [FormInputComponent], | ||
imports: [CommonModule, FontAwesomeModule, FormsModule, ReactiveFormsModule], | ||
exports: [FormInputComponent], | ||
}) | ||
export class ComponentsModule { | ||
constructor(private library: FaIconLibrary) { | ||
library.addIcons(faExclamationCircle); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/app/component-library/components/form-input/form-input.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<!-- @format --> | ||
<div | ||
class="input-vertical-label" | ||
[ngClass]="[ | ||
'input-vertical-label-' + variant, | ||
isLabelHidden() ? 'hidden' : '' | ||
]" | ||
> | ||
{{ placeholder }} | ||
</div> | ||
<fa-icon | ||
*ngIf="errors" | ||
class="error-icon" | ||
[icon]="['fas', 'exclamation-circle']" | ||
></fa-icon> | ||
<input | ||
[type]="type" | ||
(input)="onInputChange($event.target.value)" | ||
class="form-control form-input" | ||
[ngClass]="[ | ||
'form-input-' + variant, | ||
config && config.validateDirty ? 'validate-dirty' : '', | ||
errors ? 'form-input-errors' : '' | ||
]" | ||
[id]="fieldName" | ||
[name]="fieldName" | ||
[placeholder]="placeholder" | ||
[formControl]="control" | ||
/> | ||
<div class="input-vertical-error" [ngClass]="{ hidden: !errors }"> | ||
{{ errors }} | ||
</div> |
93 changes: 93 additions & 0 deletions
93
src/app/component-library/components/form-input/form-input.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* @format */ | ||
@import 'fonts'; | ||
@import 'variables'; | ||
|
||
:host { | ||
position: relative; | ||
} | ||
|
||
.form-input { | ||
border-radius: 12px; | ||
font-family: 'UsualRegular', sans-serif; | ||
font-size: 14px; | ||
line-height: 24px; | ||
font-weight: 600; | ||
border: 1px solid $form-border; | ||
padding: 12px; | ||
height: 48px; | ||
|
||
&-errors { | ||
background-color: $form-background-error; | ||
border: 1px solid $form-border-error; | ||
} | ||
|
||
&:focus { | ||
box-shadow: none; | ||
background-color: $form-background-focus; | ||
border: 1px solid $form-border-focus; | ||
} | ||
|
||
&::placeholder { | ||
text-transform: uppercase; | ||
font-family: 'UsualRegular', sans-serif; | ||
line-height: 16px; | ||
letter-spacing: 16%; | ||
font-weight: 400; | ||
font-size: 10px; | ||
} | ||
|
||
&-default { | ||
color: $form-default-text-color; | ||
} | ||
|
||
&-dark { | ||
background-color: $form-background-dark; | ||
opacity: 0.8; | ||
color: white; | ||
border: 1px solid transparent; | ||
&:focus { | ||
background-color: $form-background-dark !important; | ||
opacity: 1; | ||
border: 1px solid $form-border; | ||
} | ||
&::placeholder { | ||
color: white; | ||
} | ||
} | ||
} | ||
|
||
.input-vertical-label { | ||
position: absolute; | ||
font-family: 'UsualRegular', sans-serif; | ||
font-size: 10px; | ||
font-weight: 400; | ||
text-transform: uppercase; | ||
left: 12px; | ||
top: 4px; | ||
&-dark { | ||
color: $form-label-dark; | ||
} | ||
} | ||
|
||
.hidden { | ||
display: none; | ||
} | ||
|
||
.input-vertical-error { | ||
font-family: 'UsualRegular', sans-serif; | ||
font-size: 12px; | ||
font-weight: 400; | ||
line-height: 16px; | ||
letter-spacing: 0em; | ||
text-align: left; | ||
color: $form-error-red; | ||
margin-left: 15px; | ||
margin-top: 5px; | ||
} | ||
|
||
.error-icon { | ||
position: absolute; | ||
right: 20px; | ||
top: 12px; | ||
color: $form-error-red; | ||
} |
161 changes: 161 additions & 0 deletions
161
src/app/component-library/components/form-input/form-input.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* @format */ | ||
import { Shallow } from 'shallow-render'; | ||
import { ComponentsModule } from '../../components.module'; | ||
import { FormInputComponent, FormInputConfig } from './form-input.component'; | ||
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms'; | ||
|
||
type AttrOnOff = 'on' | 'off'; | ||
|
||
describe('FormInputComponent', () => { | ||
let shallow: Shallow<FormInputComponent>; | ||
|
||
beforeEach(async () => { | ||
shallow = new Shallow(FormInputComponent, ComponentsModule).import( | ||
ReactiveFormsModule | ||
); | ||
}); | ||
|
||
it('should create with an empty control', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
const { instance } = await shallow.render({ | ||
bind: { control: mockControl }, | ||
}); | ||
|
||
expect(instance).toBeTruthy(); | ||
}); | ||
|
||
it('should create with a control', async () => { | ||
const mockControl = new UntypedFormControl('input'); | ||
const { instance } = await shallow.render({ | ||
bind: { control: mockControl }, | ||
}); | ||
|
||
expect(instance).toBeTruthy(); | ||
}); | ||
|
||
it('should bind the input type with empty form control', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
const { find } = await shallow.render({ | ||
bind: { type: 'password', control: mockControl }, | ||
}); | ||
|
||
const inputElement = find('input').nativeElement; | ||
|
||
expect(inputElement.type).toBe('password'); | ||
}); | ||
|
||
it('should bind the input type with form control', async () => { | ||
const control = new UntypedFormControl('input'); | ||
const { find } = await shallow.render({ | ||
bind: { control: control, type: 'password' }, | ||
}); | ||
|
||
const inputElement = find('input').nativeElement; | ||
|
||
expect(inputElement.type).toBe('password'); | ||
}); | ||
|
||
it('should hide label for empty number inputs', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
|
||
const { instance } = await shallow.render({ | ||
bind: { type: 'number', control: mockControl }, | ||
}); | ||
|
||
expect(instance.isLabelHidden()).toBeTrue(); | ||
}); | ||
|
||
it('should show label for non-empty text inputs', async () => { | ||
const mockControl = new UntypedFormControl('Some text'); | ||
|
||
const { instance } = await shallow.render({ | ||
bind: { type: 'text', control: mockControl }, | ||
}); | ||
|
||
expect(instance.isLabelHidden()).toBeFalse(); | ||
}); | ||
|
||
it('should set input attributes based on config', async () => { | ||
const mockControl = new UntypedFormControl('Some text'); | ||
|
||
const config: FormInputConfig = { | ||
autocorrect: 'off', | ||
autocapitalize: 'off', | ||
spellcheck: 'off', | ||
}; | ||
const { find } = await shallow.render({ | ||
bind: { config, control: mockControl }, | ||
}); | ||
|
||
const inputElement = find('input').nativeElement; | ||
|
||
expect(inputElement.getAttribute('autocorrect')).toBe(config.autocorrect); | ||
|
||
expect(inputElement.getAttribute('autocapitalize')).toBe( | ||
config.autocapitalize | ||
); | ||
|
||
expect(inputElement.getAttribute('spellcheck')).toBe(config.spellcheck); | ||
}); | ||
|
||
it('should emit valueChange event on input value change', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
|
||
const mockValue = 'test value'; | ||
const { instance, find } = await shallow.render({ | ||
bind: { control: mockControl }, | ||
}); | ||
|
||
const spy = spyOn(instance.valueChangeSubject, 'next'); | ||
|
||
instance.onInputChange(mockValue); | ||
|
||
expect(spy).toHaveBeenCalledWith(mockValue); | ||
}); | ||
|
||
it('should apply right-align class based on config', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
const { instance, fixture } = await shallow.render({ | ||
bind: { config: { textAlign: 'right' }, control: mockControl }, | ||
}); | ||
fixture.detectChanges(); | ||
|
||
expect(instance.rightAlign).toBeTrue(); | ||
}); | ||
it('should bind the placeholder attribute to the input element', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
const placeholderValue = 'Enter text here'; | ||
const { find } = await shallow.render({ | ||
bind: { placeholder: placeholderValue, control: mockControl }, | ||
}); | ||
|
||
const inputElement = find('input').nativeElement; | ||
|
||
expect(inputElement.placeholder).toBe(placeholderValue); | ||
}); | ||
|
||
it('should return the correct error from the validation array', async () => { | ||
const mockControl = new UntypedFormControl(''); | ||
const { instance } = await shallow.render({ | ||
bind: { | ||
validators: [ | ||
{ | ||
validation: 'minLength', | ||
message: 'Must be at least 3 characters', | ||
value: 3, | ||
}, | ||
{ | ||
validation: 'maxLength', | ||
message: 'Must be at most 10 characters', | ||
value: 10, | ||
}, | ||
], | ||
control: mockControl, | ||
}, | ||
}); | ||
|
||
const errorMessage = instance.getInputErrorFromValue('aa') | ||
|
||
expect(errorMessage).toBe('Must be at least 3 characters'); | ||
}); | ||
}); |
Oops, something went wrong.