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: textarea directive #264

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
26,017 changes: 26,017 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions projects/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
},
"./tag/styles": {
"sass": "./tag/styles/_index.scss"
},
"./textarea/styles": {
"sass": "./textarea/styles/_index.scss"
}
}
}
1 change: 1 addition & 0 deletions projects/lib/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
@forward './skeleton/styles';
@forward './spinner/styles';
@forward './tag/styles';
@forward './textarea/styles';
//@forward './tooltip';
8 changes: 8 additions & 0 deletions projects/lib/textarea/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

export * from './public-api';
Empty file.
9 changes: 9 additions & 0 deletions projects/lib/textarea/public-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

export * from './textarea.directive';
export * from './textarea.module';
52 changes: 52 additions & 0 deletions projects/lib/textarea/styles/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@forward './vars';

textarea.wr-textarea {
--wr-textarea-padding-right: var(--wr-textarea-padding-x);
--wr-textarea-cursor: default;
--wr-textarea-resize: none;

display: block;
width: 100%;

cursor: var(--wr-textarea-cursor);

color: var(--wr-textarea-color);
font-size: var(--wr-textarea-font-size);
font-weight: var(--wr-textarea-font-weight);
line-height: var(--wr-textarea-line-height);

background-color: var(--wr-textarea-bg-color);
border: 1px solid var(--wr-textarea-border-color);
border-radius: var(--wr-textarea-border-radius);
box-shadow: var(--wr-textarea-box-shadow);

padding: var(--wr-textarea-padding-y) var(--wr-textarea-padding-x);
padding-right: var(--wr-textarea-padding-right);

transition: var(--wr-transition-base);

&.wr-textarea--disabled {
--wr-textarea-cursor: not-allowed;
--wr-textarea-color: var(--wr-color-medium);
--wr-textarea-bg-color: var(--wr-color-light-lighter);
}

&.wr-textarea--resizable {
--wr-textarea-resize: both;
resize: var(--wr-textarea-resize);
}

&.wr-textarea--autosize {
overflow-y: hidden;
resize: none;
}

&::placeholder {
color: var(--wr-textarea-placeholder-color);
font-style: italic;
}

&:disabled {
cursor: var(--wr-textarea-cursor);
}
}
17 changes: 17 additions & 0 deletions projects/lib/textarea/styles/vars.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
:root {
--wr-textarea-color: var(--wr-color-dark);
--wr-textarea-placeholder-color: var(--wr-color-light);
--wr-textarea-suffix-prefix-color: var(--wr-color-medium);
--wr-textarea-bg-color: var(--wr-color-white);
--wr-textarea-border-color: var(--wr-color-light-lighter);
--wr-textarea-border-radius: 0.375rem;
--wr-textarea-box-shadow: none;
--wr-textarea-icon-size: 1.25rem;
--wr-textarea-icon-color: var(--wr-color-medium);
--wr-textarea-font-size: 0.875rem;
--wr-textarea-font-weight: 400;
--wr-textarea-font-family: var(--wr-font-family-base);
--wr-textarea-line-height: 1.25rem;
--wr-textarea-padding-y: 0.375rem;
--wr-textarea-padding-x: 0.825rem;
}
111 changes: 111 additions & 0 deletions projects/lib/textarea/textarea.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
AfterViewInit,
booleanAttribute,
ChangeDetectorRef,
Directive,
ElementRef,
HostListener,
inject,
Input,
signal,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { noop } from 'rxjs';

@Directive({
selector: 'textarea[wr-textarea]',
standalone: true,
host: {
'[class.wr-textarea]': 'true',
'[class.wr-textarea--resizable]': 'resizable',
'[class.wr-textarea--disabled]': 'isDisabled()',
'[class.wr-textarea--autosize]': 'autosize',
'[attr.disabled]': 'isDisabled() || null',
'[attr.readonly]': 'readonly || null'
},
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: WrTextareaDirective,
multi: true,
},
],
})
export class WrTextareaDirective implements ControlValueAccessor, AfterViewInit {
@Input({ transform: booleanAttribute }) readonly = false;
@Input({ transform: booleanAttribute }) resizable = true;
@Input({ transform: booleanAttribute }) autosize = false;

@HostListener("input", ["$event"])
public onInput(event: Event): void {
const value = (event.target as HTMLTextAreaElement).value;
this.onChange(value);

if (this.autosize) {
this.resizeTextarea();
}
}

@HostListener("blur")
public onBlur(): void {
this.onTouch();
}

protected readonly isDisabled = signal(false);
private readonly element: HTMLTextAreaElement;

private readonly elementRef: ElementRef<HTMLTextAreaElement> = inject(ElementRef);
private readonly cdr = inject(ChangeDetectorRef);

constructor() {
this.element = this.elementRef.nativeElement;
}

ngAfterViewInit(): void {
if (this.autosize) {
this.resizeTextarea();
}
}

onChange: (value: string) => void = noop;
onTouch: () => void = noop;

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

registerOnTouched(fn: () => void): void {
this.onTouch = fn;
}

setDisabledState(isDisabled: boolean): void {
this.isDisabled.set(coerceBooleanProperty(isDisabled));
}

writeValue(value: string): void {
this.element.value = value ?? "";

requestAnimationFrame(() => {
this.resizeTextarea();
});

this.cdr.markForCheck();
}

private resizeTextarea(): void {
if (!this.autosize) {
return;
}

this.element.style.height = "auto";
this.element.style.height = `${this.element.scrollHeight}px`;
}
}
16 changes: 16 additions & 0 deletions projects/lib/textarea/textarea.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
*/

import { NgModule } from '@angular/core';

import { WrTextareaDirective } from './textarea.directive';

@NgModule({
imports: [WrTextareaDirective],
exports: [WrTextareaDirective],
})
export class WrTextareaModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class SidebarComponent extends WrAbstractBase implements OnInit {
{ title: 'Skeleton', url: [routes.docs.components.index, routes.docs.components.skeleton] },
{ title: 'Spinner', url: [routes.docs.components.index, routes.docs.components.spinner] },
{ title: 'Tag', url: [routes.docs.components.index, routes.docs.components.tag] },
{ title: 'Textarea', url: [routes.docs.components.index, routes.docs.components.textarea] },
],
},
];
Expand Down
4 changes: 4 additions & 0 deletions projects/showcase/app/docs/components/components.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ export default [
path: components.tag,
loadComponent: () => import('./tag/tag.component').then(c => c.TagComponent),
},
{
path: components.textarea,
loadComponent: () => import('./textarea/textarea.component').then(c => c.TextareaComponent),
},
] satisfies Routes;
117 changes: 117 additions & 0 deletions projects/showcase/app/docs/components/textarea/textarea.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<div class="ngwr-page-info">
<div class="ngwr-page-info__tags">
<wr-tag transparent>Component</wr-tag>
<wr-tag transparent color="secondary">Standalone</wr-tag>
</div>
<h1 class="ngwr-page-info__title">{{ title }}</h1>
<p class="ngwr-page-info__description">{{ description }}</p>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">How to install</h2>
<p class="ngwr-page-section__description">
Import <code class="ngwr-pre">WrTextareaDirective</code> into a component where you want to use.
</p>
<div class="ngwr-page-section__content">
<ngwr-code [code]="code.import" codeLang="ts"></ngwr-code>
<ngwr-code [code]="code.component" codeLang="ts"></ngwr-code>
</div>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">Basic usage</h2>
<p class="ngwr-page-section__description">
Basic usage of textarea component
</p>

<div class="ngwr-page-section__content">
<ngwr-snippet>
<ng-container ngProjectAs="[body]">
<textarea wr-textarea></textarea>
</ng-container>
<ngwr-code [code]="code.basic" codeLang="html" code></ngwr-code>
</ngwr-snippet>
</div>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">Autosize</h2>
<p class="ngwr-page-section__description">
Textarea with Autosize
</p>

<div class="ngwr-page-section__content">
<ngwr-snippet>
<ng-container ngProjectAs="[body]">
<textarea wr-textarea [autosize]="true"></textarea>
</ng-container>
<ngwr-code [code]="code.autosize" codeLang="html" code></ngwr-code>
</ngwr-snippet>
</div>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">Disabled state</h2>
<p class="ngwr-page-section__description">
You can add <code class="ngwr-pre">disabled</code> attribute to prevent interacting with the textarea.
</p>

<div class="ngwr-page-section__content">
<ngwr-snippet>
<ng-container ngProjectAs="[body]">
<textarea wr-textarea [formControl]="disabledFormControl"></textarea>
</ng-container>
<ngwr-code [code]="code.disabled" codeLang="html" slot="code"></ngwr-code>
</ngwr-snippet>
</div>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">Readonly state</h2>
<p class="ngwr-page-section__description">
You can add <code class="ngwr-pre">readonly</code> attribute to prevent change of with the textarea.
</p>

<div class="ngwr-page-section__content">
<ngwr-snippet>
<ng-container ngProjectAs="[body]">
<textarea wr-textarea readonly></textarea>
</ng-container>
<ngwr-code [code]="code.readonly" codeLang="html" slot="code"></ngwr-code>
</ngwr-snippet>
</div>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">Styling</h2>
<p class="ngwr-page-section__description">You can change textarea styles with css variables.</p>

<div class="ngwr-page-section__content">
<ngwr-code [code]="code.styling" codeLang="scss"></ngwr-code>
</div>
</div>

<div class="ngwr-page-section">
<h2 class="ngwr-page-section__title">API</h2>

<div class="ngwr-page-section__content">
<div class="ngwr-page-table">
<table>
<tr class="ngwr-page-table__head">
<th>Property</th>
<th>Description</th>
<th>Type</th>
<th>Required</th>
<th>Default</th>
</tr>
<tr>
<td><code>[autosize]</code></td>
<td>enable autosize for textarea</td>
<td><code>boolean</code></td>
<td></td>
<td><code>false</code></td>
</tr>
</table>
</div>
</div>
</div>
Loading