Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
feat(tag): Add tag-add-form for custom forms.
Browse files Browse the repository at this point in the history
  • Loading branch information
myieye authored and ffriedl89 committed May 31, 2021
1 parent ec5b53e commit 757b4b1
Show file tree
Hide file tree
Showing 21 changed files with 695 additions and 202 deletions.
12 changes: 7 additions & 5 deletions apps/demos/src/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ import {
DtExampleCalendarMinMax,
DtExampleTimepickerMinMax,
DtExampleDatepickerDark,
DtExampleDatepickerDefault
DtExampleDatepickerDefault,
DtExampleCustomAddFormTag,
} from '@dynatrace/barista-examples';

// The Routing Module replaces the routing configuration in the root or feature module.
Expand Down Expand Up @@ -593,10 +594,10 @@ const ROUTES: Routes = [
{ path: 'drawer-dynamic-example', component: DtExampleDrawerDynamic },
{ path: 'drawer-nested-example', component: DtExampleDrawerNested },
{ path: 'drawer-over-example', component: DtExampleDrawerOver },
{ path: 'calendar-min-max-example', component: DtExampleCalendarMinMax},
{ path: 'timepicker-min-max-example', component: DtExampleTimepickerMinMax},
{ path: 'datepicker-dark-example', component: DtExampleDatepickerDark},
{ path: 'datepicker-default-example', component: DtExampleDatepickerDefault},
{ path: 'calendar-min-max-example', component: DtExampleCalendarMinMax },
{ path: 'timepicker-min-max-example', component: DtExampleTimepickerMinMax },
{ path: 'datepicker-dark-example', component: DtExampleDatepickerDark },
{ path: 'datepicker-default-example', component: DtExampleDatepickerDefault },
{
path: 'drawer-table-default-example',
component: DtExampleDrawerTableDefault,
Expand Down Expand Up @@ -1187,6 +1188,7 @@ const ROUTES: Routes = [
path: 'select-custom-value-template-example',
component: DtExampleSelectCustomValueTemplate,
},
{ path: 'tag-custom-add-form-example', component: DtExampleCustomAddFormTag },
];

@NgModule({
Expand Down
4 changes: 4 additions & 0 deletions apps/demos/src/nav-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,10 @@ export const DT_DEMOS_EXAMPLE_NAV_ITEMS = [
name: 'tag-removable-example',
route: '/tag-removable-example',
},
{
name: 'tag-custom-add-form-example',
route: '/tag-custom-add-form-example',
},
],
},
{
Expand Down
6 changes: 6 additions & 0 deletions libs/barista-components/tag/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ng_module_view_engine(
":src/tag.html",
":src/tag-add/tag-add.html",
":src/tag-list/tag-list.html",
":src/tag-add/tag-add-form/tag-add-form.html",
],
module_name = "@dynatrace/barista-components/tag",
tsconfig = "tsconfig_lib",
Expand All @@ -44,7 +45,9 @@ ng_module_view_engine(
"@npm//@angular/cdk",
"@npm//@angular/common",
"@npm//@angular/core",
"@npm//@angular/forms",
"@npm//rxjs",
"@npm//lodash-es",
],
)

Expand All @@ -54,6 +57,7 @@ multi_sass_binary(
"src/tag.scss",
"src/tag-add/tag-add.scss",
"src/tag-list/tag-list.scss",
"src/tag-add/tag-add-form/tag-add-form.scss",
],
)

Expand All @@ -72,11 +76,13 @@ jest(
":compile",
"//libs/barista-components/core:compile",
"//libs/barista-components/icon:compile",
"//libs/barista-components/input:compile",
"//libs/testing/browser",
"@npm//@angular/cdk",
"@npm//@angular/common",
"@npm//@angular/core",
"@npm//@angular/platform-browser",
"@npm//@angular/forms",
],
)

Expand Down
69 changes: 39 additions & 30 deletions libs/barista-components/tag/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,51 +49,60 @@ The `dt-tag-list` element evaluates whether an amount of `dt-tag` elements fit
in one line and displays a 'more' button when it doesn't fit. If provided
`dt-tag-add` will always be displayed at the end of the `dt-tag-list`.

## Inputs
### Inputs

| Name | Type | Default | Description |
| ------------ | -------- | ------------ | ------------------------------------------------------------------------- |
| `aria-label` | `string` | `undefinded` | `Used to set the 'aria-label' attribute on the underlying input element.` |
| Name | Type | Default | Description |
| ------------ | -------- | ------------ | ----------------------------------------------------------------------- |
| `aria-label` | `string` | `undefinded` | Used to set the 'aria-label' attribute on the underlying input element. |

## Examples
## Tag add button

### Removable state
The `dt-tag-add` button allows manual tag entries to an entity. The tag add
button should be placed inside the `dt-tag-list` wrapper and after your `dt-tag`
elements.

<ba-live-example name="DtExampleTagRemovable"></ba-live-example>
<ba-live-example name="DtExampleTagListWithTagAdd"></ba-live-example>

### With key/category
### Inputs

<ba-live-example name="DtExampleTagKey"></ba-live-example>
| Name | Type | Default | Description |
| ------------- | -------- | ------------ | ----------------------------------------------------------------------- |
| `placeholder` | `string` | `undefined` | Placeholder string for the add tag input overlay. |
| `aria-label` | `string` | `undefinded` | Used to set the 'aria-label' attribute on the underlying input element. |

### Interactive example
### Outputs

<ba-live-example name="DtExampleTagInteractive"></ba-live-example>
| Name | Type | Description |
| ---------- | --------------------- | -------------------------------- |
| `tagAdded` | `EventEmitter<event>` | Emits event when a tag is added. |

## Tag add button
### Methods

The `dt-tag-add` button allows manual tag entries to an entity.
| Name | Type | Description |
| ---------- | ------ | --------------------------------------------------- |
| `open()` | `void` | Opens the input overlay. |
| `close()` | `void` | Closes the input overlay. |
| `submit()` | `void` | Triggers `tagAdded` **if** the input/form is valid. |

<ba-live-example name="DtExampleTagListWithTagAdd"></ba-live-examples>
## Custom tag add form

The tag add button should be placed inside the `dt-tag-list` wrapper and after
your `dt-tag` elements.
A `dt-tag-add-form` can be used inside `dt-tag-add` to use a custom form inside
the Add tag overlay. If the `dt-tag-add-form` contains a `FormGroupDirective`
(from `@angular/forms`) its validity will be used to enable/disable submitting
the form.

### Inputs
<ba-live-example name="DtExampleCustomAddFormTag"></ba-live-example>

| Name | Type | Default | Description |
| ------------- | -------- | ------------ | ------------------------------------------------------------------------- |
| `placeholder` | `string` | `undefined` | `Placeholder string for the add tag input overlay.` |
| `aria-label` | `string` | `undefinded` | `Used to set the 'aria-label' attribute on the underlying input element.` |
## Examples

### Outputs
### Removable state

| Name | Type | Description |
| ---------- | --------------------- | -------------------------------- |
| `tagAdded` | `EventEmitter<event>` | Emits event when a tag is added. |
<ba-live-example name="DtExampleTagRemovable"></ba-live-example>

### Methods
### With key/category

<ba-live-example name="DtExampleTagKey"></ba-live-example>

### Interactive example

| Name | Type | Description |
| --------- | ------ | --------------------------- |
| `open()` | `void` | `Opens the input overlay.` |
| `close()` | `void` | `Closes the input overlay.` |
<ba-live-example name="DtExampleTagInteractive"></ba-live-example>
1 change: 1 addition & 0 deletions libs/barista-components/tag/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './src/tag-module';
export * from './src/tag';
export * from './src/tag-list/tag-list';
export * from './src/tag-add/tag-add';
export * from './src/tag-add/tag-add-form/tag-add-form';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<ng-content></ng-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
display: block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* @license
* Copyright 2021 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { By } from '@angular/platform-browser';

import { DtTagAddForm } from './tag-add-form';

describe('DtTagAddForm', () => {
let component: DtTagAddFormComponent;
let fixture: ComponentFixture<DtTagAddFormComponent>;
let addTagFormInstance: DtTagAddForm;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DtTagAddForm, DtTagAddFormComponent],
imports: [FormsModule, ReactiveFormsModule],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(DtTagAddFormComponent);
component = fixture.componentInstance;
addTagFormInstance = fixture.debugElement.query(By.directive(DtTagAddForm))
.componentInstance;
fixture.detectChanges();
});

it('should emit current validity and changes to the validity of a provided form-group', () => {
const validSpy = jest.fn();
const sub = addTagFormInstance.valid$.subscribe(validSpy);
fixture.detectChanges();

expect(validSpy).toHaveBeenCalledTimes(1);
expect(validSpy).toHaveBeenLastCalledWith(false);

component.keyFormControl.setValue('1234');

expect(validSpy).toHaveBeenCalledTimes(2);
expect(validSpy).toHaveBeenLastCalledWith(true);

component.keyFormControl.setValue('123');

expect(validSpy).toHaveBeenCalledTimes(3);
expect(validSpy).toHaveBeenLastCalledWith(false);

sub.unsubscribe();
});
});

/** Test component that passes a form-group to dt-tag-add-form. */
@Component({
selector: 'dt-test-app',
template: `
<dt-tag-add-form>
<form [formGroup]="form" class="key-value-form">
<input
#key
type="text"
aria-label="Tag key"
required
formControlName="key"
/>
</form>
</dt-tag-add-form>
`,
})
class DtTagAddFormComponent {
readonly keyFormControl = new FormControl('', [
// tslint:disable-next-line: no-unbound-method
Validators.minLength(4),
]);

readonly form = new FormGroup({
key: this.keyFormControl,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @license
* Copyright 2021 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
Component,
ContentChild,
ContentChildren,
ElementRef,
NgZone,
QueryList,
} from '@angular/core';
import { FormGroupDirective } from '@angular/forms';
import { DtInput } from '@dynatrace/barista-components/input';
import { Observable, of } from 'rxjs';
import { map, share, startWith, switchMap, take } from 'rxjs/operators';

@Component({
selector: 'dt-tag-add-form',
templateUrl: './tag-add-form.html',
styleUrls: ['./tag-add-form.scss'],
})
export class DtTagAddForm {
/** @internal ElementRef of Add Tag Input */
@ContentChildren(DtInput, { read: ElementRef, descendants: true })
_inputs: QueryList<ElementRef<HTMLInputElement>>;

/** @internal The FormGroup of the custom form for adding tags */
@ContentChild(FormGroupDirective) _form: FormGroupDirective;

/** Emits whether the form input is valid based on a FormGroupDirective. */
readonly valid$: Observable<boolean> = this._ngZone.onStable.pipe(
take(1),
switchMap(() =>
(this._form?.statusChanges ?? of()).pipe(startWith(undefined)),
),
map(() => this._form.valid ?? true),
share(),
);

constructor(private readonly _ngZone: NgZone) {}

/** @internal Resets the provided FormGroup */
_reset(): void {
this._form?.resetForm();
}
}
Loading

0 comments on commit 757b4b1

Please sign in to comment.