Skip to content

Commit

Permalink
feat(experimental): Textfield add new component (#6298)
Browse files Browse the repository at this point in the history
Co-authored-by: taiga-family-bot <[email protected]>
  • Loading branch information
waterplea and taiga-family-bot authored Dec 21, 2023
1 parent b92feb5 commit f72522b
Show file tree
Hide file tree
Showing 32 changed files with 982 additions and 20 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"retrowave",
"replicants",
"tuiiconbutton",
"hitbox"
"hitbox",
"Textfieldd"
],
"ignoreRegExpList": ["\\(https?://.*?\\)", "\\/{1}.+\\/{1}", "\\%2F.+", "\\%2C.+", "\\ɵ.+", "\\ыва.+"],
"overrides": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {AbstractControl, NG_VALIDATORS, Validator} from '@angular/forms';
],
})
export class TuiNativeValidatorDirective implements Validator {
private readonly el: HTMLInputElement = inject(ElementRef).nativeElement;
private readonly host: HTMLInputElement = inject(ElementRef).nativeElement;
private readonly zone = inject(NgZone);
private control?: AbstractControl;

Expand All @@ -33,4 +33,8 @@ export class TuiNativeValidatorDirective implements Validator {

return null;
}

private get el(): HTMLInputElement {
return this.host.querySelector('input,textarea,select') || this.host;
}
}
17 changes: 15 additions & 2 deletions projects/cdk/services/directive-styles.service.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import {
ComponentFactoryResolver,
createComponent,
EnvironmentInjector,
Inject,
inject,
Injectable,
INJECTOR,
Injector,
Type,
} from '@angular/core';

// TODO: Add cleanup with DestroyRef in Angular 16+ and replace service with just a map from a token
export function tuiWithStyles(component: Type<unknown>): void {
const map = inject(TuiDirectiveStylesService).map;
const environmentInjector = inject(EnvironmentInjector);

if (!map.has(component)) {
map.set(component, createComponent(component, {environmentInjector}));
}
}

/**
* Service to use styles with directives
* @deprecated use {@link tuiWithStyles} instead
*/
@Injectable({
providedIn: 'root',
})
export class TuiDirectiveStylesService {
private readonly map = new Map<Type<unknown>, unknown>();
readonly map = new Map();

constructor(
@Inject(ComponentFactoryResolver)
Expand Down
16 changes: 10 additions & 6 deletions projects/core/directives/dropdown/dropdown-open.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,18 @@ export class TuiDropdownOpenDirective implements OnChanges {
@HostListener('document:keydown.silent', ['$event'])
onKeydown({key, target, defaultPrevented}: KeyboardEvent): void {
if (
!defaultPrevented &&
tuiIsEditingKey(key) &&
this.editable &&
tuiIsHTMLElement(target) &&
!tuiIsElementEditable(target)
defaultPrevented ||
!tuiIsEditingKey(key) ||
!this.editable ||
!this.focused ||
!tuiIsHTMLElement(target) ||
(tuiIsElementEditable(target) && target !== this.host)
) {
this.host.focus({preventScroll: true});
return;
}

this.update(true);
this.host.focus({preventScroll: true});
}

ngOnChanges(): void {
Expand Down
11 changes: 3 additions & 8 deletions projects/core/directives/hint/hint-describe.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {tuiAsDriver, TuiDriver} from '@taiga-ui/core/abstract';
import {
debounce,
distinctUntilChanged,
fromEvent,
map,
merge,
of,
Expand All @@ -30,13 +31,7 @@ export class TuiHintDescribeDirective extends TuiDriver implements OnChanges {
private readonly id$ = new ReplaySubject(1);

private readonly stream$ = this.id$.pipe(
tuiIfMap(
() =>
tuiTypedFromEvent(this.doc, 'keydown', {
capture: true,
}),
tuiIsPresent,
),
tuiIfMap(() => fromEvent(this.doc, 'keydown', {capture: true}), tuiIsPresent),
switchMap(() =>
this.focused
? of(false)
Expand All @@ -53,7 +48,7 @@ export class TuiHintDescribeDirective extends TuiDriver implements OnChanges {
);

@Input()
tuiHintDescribe: string | '' | null = '';
tuiHintDescribe?: string | null = '';

readonly type = 'hint';

Expand Down
1 change: 1 addition & 0 deletions projects/core/styles/theme/appearance.less
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
@import 'appearance/primary.less';
@import 'appearance/secondary.less';
@import 'appearance/status.less';
@import 'appearance/textfield.less';
35 changes: 35 additions & 0 deletions projects/core/styles/theme/appearance/textfield.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import '../../taiga-ui-local.less';

[tuiAppearance][data-appearance='textfield'] {
--t-shadow: 0 0.125rem 0.1875rem rgba(0, 0, 0, 0.1);
background: var(--tui-base-01);
color: var(--tui-text-01);
box-shadow: var(--t-shadow);
outline: 1px solid var(--tui-base-03);
outline-offset: -1px;

&:valid[data-invalid='true'],
&:invalid:not([data-invalid='false']) {
outline-color: var(--tui-negative);
}

&:read-only {
box-shadow: none;
outline-color: var(--tui-base-04) !important;
}
.transition(~'box-shadow, background, outline-color');

.appearance-hover({
--t-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.16);
});

.appearance-focus({
box-shadow: none;
outline: 0.125rem solid var(--tui-primary);
outline-offset: -0.125rem;
});

.appearance-disabled({
box-shadow: none;
});
}
9 changes: 9 additions & 0 deletions projects/demo/src/modules/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,15 @@ export const ROUTES: Routes = [
title: 'Surface',
},
},
{
path: 'experimental/textfield',
loadChildren: async () =>
(await import('../experimental/textfield/textfield.module'))
.ExampleTuiTextfieldModule,
data: {
title: 'Textfield',
},
},
{
path: 'experimental/thumbnail-card',
loadChildren: async () =>
Expand Down
6 changes: 6 additions & 0 deletions projects/demo/src/modules/app/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,12 @@ export const pages: TuiDocPages = [
keywords: 'card, container, wrapper, image, blur, overlay',
route: '/experimental/surface',
},
{
section: 'Experimental',
title: 'Textfield',
keywords: 'form, input, select, textarea, combobox, ввод, форма, поле',
route: '/experimental/textfield',
},
{
section: 'Experimental',
title: 'Title',
Expand Down
195 changes: 195 additions & 0 deletions projects/demo/src/modules/experimental/textfield/examples/1/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<div [tuiTextfieldCleaner]="true">
<h2 class="tui-heading-5">Label inside</h2>
<tui-textfield
*ngFor="let size of sizes"
iconLeft="tuiIconSearchLarge"
iconRight="tuiIconSettingsLarge"
[filler]="filler"
[tuiTextfieldSize]="size"
>
<label tuiLabel>I am a label</label>
<input
placeholder="I am placeholder"
tuiTextfieldd
[(ngModel)]="value"
/>
<tui-icon icon="tuiIconBellLarge"></tui-icon>
<tui-tooltip content="I am a hint"></tui-tooltip>
</tui-textfield>

<h2 class="tui-heading-5">Label outside</h2>
<label
*ngFor="let size of sizes"
tuiLabel
>
For long labels it is better to use label outside as it can wrap to multiple lines
<tui-textfield
#textfield
iconLeft="tuiIconSearchLarge"
iconRight="tuiIconSettingsLarge"
[filler]="filler"
[tuiTextfieldSize]="size"
>
<input
tuiTextfieldd
[placeholder]="textfield.focused ? 'I am placeholder' : 'I am a short label'"
[(ngModel)]="value"
/>
<tui-icon icon="tuiIconBellLarge"></tui-icon>
<tui-tooltip content="I am a hint"></tui-tooltip>
</tui-textfield>
</label>

<h2 class="tui-heading-5">Disabled</h2>
<tui-textfield
iconLeft="tuiIconSearchLarge"
iconRight="tuiIconSettingsLarge"
[filler]="filler"
>
<label tuiLabel>I am a label</label>
<input
placeholder="I am placeholder"
tuiTextfieldd
[disabled]="true"
[(ngModel)]="value"
/>
<tui-icon icon="tuiIconBellLarge"></tui-icon>
<tui-tooltip content="I am a hint"></tui-tooltip>
</tui-textfield>

<h2 class="tui-heading-5">Read only</h2>
<tui-textfield
iconLeft="tuiIconSearchLarge"
iconRight="tuiIconSettingsLarge"
[filler]="filler"
>
<label tuiLabel>I am a label</label>
<input
placeholder="I am placeholder"
tuiTextfieldd
[readOnly]="true"
[(ngModel)]="value"
/>
<tui-icon icon="tuiIconBellLarge"></tui-icon>
<tui-tooltip content="I am a hint"></tui-tooltip>
</tui-textfield>

<h2 class="tui-heading-5">Invalid</h2>
<tui-textfield
iconLeft="tuiIconSearchLarge"
iconRight="tuiIconSettingsLarge"
[filler]="filler"
>
<label tuiLabel>I am a label</label>
<input
placeholder="I am placeholder"
tuiTextfieldd
[invalid]="true"
[(ngModel)]="value"
/>
<tui-icon icon="tuiIconBellLarge"></tui-icon>
<tui-tooltip content="I am a hint"></tui-tooltip>
</tui-textfield>
<tui-textfield
#textfield
iconLeft="tuiIconSearchLarge"
iconRight="tuiIconSettingsLarge"
[filler]="filler"
>
<input
tuiTextfieldd
[invalid]="true"
[placeholder]="textfield.focused ? 'I am placeholder' : 'I am a short label'"
[(ngModel)]="value"
/>
<tui-icon icon="tuiIconBellLarge"></tui-icon>
<tui-tooltip content="I am a hint"></tui-tooltip>
</tui-textfield>
</div>

<div [tuiTextfieldCleaner]="true">
<h2 class="tui-heading-5">&nbsp;</h2>
<tui-primitive-textfield
*ngFor="let size of sizes"
tuiHintContent="I am a hint"
tuiTextfieldCustomContent="tuiIconBellLarge"
tuiTextfieldIcon="tuiIconSettingsLarge"
tuiTextfieldIconLeft="tuiIconSearchLarge"
[tuiTextfieldFiller]="filler"
[tuiTextfieldSize]="size"
[(value)]="value"
>
I am a label
</tui-primitive-textfield>

<h2 class="tui-heading-5">&nbsp;</h2>
<label
*ngFor="let size of sizes"
tuiLabel
>
For long labels it is better to use label outside as it can wrap to multiple lines
<tui-primitive-textfield
tuiHintContent="I am a hint"
tuiTextfieldCustomContent="tuiIconBellLarge"
tuiTextfieldIcon="tuiIconSettingsLarge"
tuiTextfieldIconLeft="tuiIconSearchLarge"
[tuiTextfieldFiller]="filler"
[tuiTextfieldLabelOutside]="true"
[tuiTextfieldSize]="size"
[(value)]="value"
>
I am a label
</tui-primitive-textfield>
</label>

<h2 class="tui-heading-5">&nbsp;</h2>
<tui-primitive-textfield
tuiHintContent="I am a hint"
tuiTextfieldCustomContent="tuiIconBellLarge"
tuiTextfieldIcon="tuiIconSettingsLarge"
tuiTextfieldIconLeft="tuiIconSearchLarge"
[disabled]="true"
[tuiTextfieldFiller]="filler"
[(value)]="value"
>
I am a label
</tui-primitive-textfield>

<h2 class="tui-heading-5">&nbsp;</h2>
<tui-primitive-textfield
tuiHintContent="I am a hint"
tuiTextfieldCustomContent="tuiIconBellLarge"
tuiTextfieldIcon="tuiIconSettingsLarge"
tuiTextfieldIconLeft="tuiIconSearchLarge"
[readOnly]="true"
[tuiTextfieldFiller]="filler"
[(value)]="value"
>
I am a label
</tui-primitive-textfield>

<h2 class="tui-heading-5">&nbsp;</h2>
<tui-primitive-textfield
tuiHintContent="I am a hint"
tuiTextfieldCustomContent="tuiIconBellLarge"
tuiTextfieldIcon="tuiIconSettingsLarge"
tuiTextfieldIconLeft="tuiIconSearchLarge"
[invalid]="true"
[tuiTextfieldFiller]="filler"
[(value)]="value"
>
I am a label
</tui-primitive-textfield>
<tui-primitive-textfield
tuiHintContent="I am a hint"
tuiTextfieldCustomContent="tuiIconBellLarge"
tuiTextfieldIcon="tuiIconSettingsLarge"
tuiTextfieldIconLeft="tuiIconSearchLarge"
[invalid]="true"
[tuiTextfieldFiller]="filler"
[tuiTextfieldLabelOutside]="true"
[(value)]="value"
>
I am a label
</tui-primitive-textfield>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:host {
display: flex;
gap: 1rem;
width: 40rem;
}

div {
flex: 1;
display: flex;
flex-direction: column;
gap: 1rem;
}
Loading

0 comments on commit f72522b

Please sign in to comment.