Skip to content

Commit

Permalink
feat(sf-input): new component
Browse files Browse the repository at this point in the history
  • Loading branch information
runyasak committed Sep 9, 2023
1 parent 5dab9e0 commit 08f75a2
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 6 deletions.
7 changes: 7 additions & 0 deletions projects/demo-app/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ export const routes: Routes = [
'./pages/example-sf-loader-circular-page/example-sf-loader-circular-page.component'
).then((mod) => mod.ExampleSfLoaderCircularPageComponent),
},
{
path: 'sf-input',
loadComponent: () =>
import('./pages/example-sf-input-page/example-sf-input-page.component').then(
(mod) => mod.ExampleSfInputPageComponent
),
},
{
path: 'forms-showcase',
data: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<app-example-wrapper [controls]="controls" [state]="prepareControlsData.state">
<label>
<span
class="typography-text-sm font-medium"
[ngClass]="{
'cursor-not-allowed text-disabled-500': prepareControlsData.state().disabled
}"
>
{{ prepareControlsData.state().label }}
</span>
<sf-input
class="w-full"
[size]="prepareControlsData.state().size"
[placeholder]="prepareControlsData.state().placeholder"
[ngModel]="prepareControlsData.state().value"
[disabled]="prepareControlsData.state().disabled"
[ngClass]="{
'!bg-disabled-100 !ring-disabled-300 !ring-1 !text-disabled-500':
prepareControlsData.state().disabled,
'!bg-disabled-100 !ring-disabled-300 !ring-1 !text-neutral-500':
prepareControlsData.state().readonly,
'ng-dirty ng-invalid': prepareControlsData.state().invalid
}"
(ngModelChange)="handleValueChange($event)"
></sf-input>
</label>
<div class="flex justify-between">
<div>
<p
*ngIf="prepareControlsData.state().invalid && !prepareControlsData.state().disabled"
class="typography-text-sm text-negative-700 font-medium mt-0.5"
>
{{ prepareControlsData.state().errorText }}
</p>
<p
*ngIf="prepareControlsData.state().helpText"
class="typography-text-xs mt-0.5"
[ngClass]="[
prepareControlsData.state().disabled ? 'text-disabled-500' : 'text-neutral-500'
]"
>
{{ prepareControlsData.state().helpText }}
</p>
<p
*ngIf="prepareControlsData.state().requiredText && prepareControlsData.state().required"
class="mt-1 typography-text-sm font-normal text-neutral-500 before:content-['*']"
>
{{ prepareControlsData.state().requiredText }}
</p>
</div>
<p
*ngIf="prepareControlsData.state().characterLimit && !prepareControlsData.state().readonly"
class="typography-text-xs mt-0.5"
[ngClass]="[
prepareControlsData.state().disabled ? 'text-disabled-500' : characterLimitClass()
]"
>
{{ charsCount() }}
</p>
</div>
</app-example-wrapper>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ExampleWrapperComponent } from '../../components/example-wrapper/example-wrapper.component';
import { SfInputComponent, SfInputSize } from 'projects/ng-storefront-ui';
import { ControlService } from '../../services/control.service';
import { Controls } from '../../components/controls/controls.types';
import { FormsModule } from '@angular/forms';

@Component({
standalone: true,
imports: [CommonModule, FormsModule, ExampleWrapperComponent, SfInputComponent],
templateUrl: './example-sf-input-page.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [ControlService],
})
export class ExampleSfInputPageComponent {
characterLimit = signal(12);

inputValue = signal('');

isAboveLimit = computed(() => this.inputValue().length > this.characterLimit());

charsCount = computed(() => this.characterLimit() - this.inputValue().length);

characterLimitClass = computed(() =>
this.isAboveLimit() ? 'text-negative-700 font-medium' : 'text-neutral-500'
);

controls: Controls = [
{
type: 'select',
modelName: 'size',
propDefaultValue: 'SfInputSize.base',
propType: 'SfInputSize',
options: Object.keys(SfInputSize),
},
{
type: 'text',
propType: 'string',
modelName: 'label',
},
{
type: 'text',
propType: 'string',
modelName: 'placeholder',
},
{
type: 'text',
propType: 'string',
modelName: 'helpText',
},
{
type: 'text',
propType: 'string',
modelName: 'requiredText',
},
{
type: 'text',
propType: 'string',
modelName: 'errorText',
},
{
type: 'text',
propType: 'number',
modelName: 'characterLimit',
},
{
type: 'boolean',
propType: 'boolean',
modelName: 'disabled',
},
{
type: 'boolean',
propType: 'boolean',
modelName: 'required',
},
{
type: 'boolean',
propType: 'boolean',
modelName: 'invalid',
description: 'Support with Reactive Forms, `ng-invalid`',
},
{
type: 'boolean',
propType: 'boolean',
modelName: 'readonly',
},
];

prepareControlsData = this.controlService.prepareControls(this.controls, {
size: SfInputSize.base,
disabled: false,
required: false,
invalid: false,
readonly: false,
placeholder: 'Placeholder text',
helpText: 'Help text',
requiredText: 'Required text',
errorText: 'Error text',
label: 'Label',
characterLimit: this.characterLimit(),
value: this.inputValue(),
});

constructor(private controlService: ControlService) {}

handleValueChange(value: string) {
console.log(this.characterLimit(), this.prepareControlsData.state().characterLimit);
this.inputValue.set(value);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<div class="p-6">
<textarea sf-textarea [formControl]="textareaControl"></textarea>
</div>
<app-example-wrapper>
<div class="flex flex-col gap-4">
<sf-input [formControl]="inputControl" placeholder="Please input here"></sf-input>
<textarea sf-textarea [formControl]="textareaControl"></textarea>
</div>
</app-example-wrapper>
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SfTextareaComponent } from 'projects/ng-storefront-ui';
import { SfInputComponent, SfTextareaComponent } from 'projects/ng-storefront-ui';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { ExampleWrapperComponent } from '../../components/example-wrapper/example-wrapper.component';

@Component({
standalone: true,
imports: [CommonModule, SfTextareaComponent, ReactiveFormsModule],
imports: [
CommonModule,
ExampleWrapperComponent,
SfInputComponent,
SfTextareaComponent,
ReactiveFormsModule,
],
templateUrl: './forms-showcase-page.component.html',
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FormsShowcasePageComponent {
inputControl = new FormControl('This input is required', { validators: Validators.required });

textareaControl = new FormControl('', { validators: Validators.required });
}
2 changes: 2 additions & 0 deletions projects/ng-storefront-ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export * from './src/lib/sf-button/sf-button.component';

export * from './src/lib/sf-counter/sf-counter.component';

export * from './src/lib/sf-input/sf-input.component';

export * from './src/lib/sf-link/sf-link.component';

export * from './src/lib/sf-list-item/sf-list-item.component';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SfInputComponent } from './sf-input.component';

describe('SfInputComponent', () => {
let component: SfInputComponent;
let fixture: ComponentFixture<SfInputComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [SfInputComponent],
});
fixture = TestBed.createComponent(SfInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
82 changes: 82 additions & 0 deletions projects/ng-storefront-ui/src/lib/sf-input/sf-input.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ChangeDetectionStrategy, Component, HostBinding, Input, forwardRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SfInputSize } from '../../types';
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
selector: 'sf-input',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<ng-content select="[prefix]" />
<input
data-testid="input-field"
class="min-w-[80px] w-full text-base outline-none appearance-none text-neutral-900 disabled:cursor-not-allowed disabled:bg-transparent read-only:bg-transparent"
[ngModel]="value"
[disabled]="disabled"
[size]="1"
[type]="type"
[placeholder]="placeholder"
(ngModelChange)="handleValueChange($event)"
/>
<ng-content select="[suffix]" />
`,
styleUrls: ['../../../styles/form-fields.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => SfInputComponent),
},
],
})
export class SfInputComponent implements ControlValueAccessor {
@Input() size: keyof typeof SfInputSize = SfInputSize.base;

@Input() value: unknown = '';

@Input() disabled: boolean = false;

@Input() type: string = '';

@Input() placeholder: string = '';

@Input() onChange: (value: unknown) => void = () => {};

@Input() onTouched: (value: unknown) => void = () => {};

sizeClasses = {
[SfInputSize.sm]: ' h-[32px]',
[SfInputSize.base]: 'h-[40px]',
[SfInputSize.lg]: 'h-[48px]',
};

@HostBinding('class') get hostClass() {
return [
'flex items-center gap-2 px-4 bg-white rounded-md ring-1 text-neutral-500 hover:ring-primary-700 focus-within:caret-primary-700 active:caret-primary-700 active:ring-primary-700 active:ring-2 focus-within:ring-primary-700 focus-within:ring-2 ring-1 ring-neutral-200',
this.sizeClasses[this.size],
].join(' ');
}

handleValueChange(value: string) {
this.writeValue(value);
this.onChange(value);
}

writeValue(value: string) {
this.value = value;
}

registerOnChange(onChange: (value: unknown) => void) {
this.onChange = onChange;
}

registerOnTouched(onTouched: (value: unknown) => void) {
this.onTouched = onTouched;
}

setDisabledState?(isDisabled: boolean) {
this.disabled = isDisabled;
}
}
2 changes: 2 additions & 0 deletions projects/ng-storefront-ui/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from './sf-counter.type';

export * from './sf-icon-base.type';

export * from './sf-input.type';

export * from './sf-loader.type';

export * from './sf-list-item.type';
Expand Down
5 changes: 5 additions & 0 deletions projects/ng-storefront-ui/src/types/sf-input.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum SfInputSize {
sm = 'sm',
base = 'base',
lg = 'lg',
}

0 comments on commit 08f75a2

Please sign in to comment.