diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html index 06c0c2e62e..f247886fd8 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.html @@ -7,41 +7,39 @@ [primaryActionBtnDisabled$]="isLoadingAddFeatureFlag$" (primaryActionBtnClicked)="onPrimaryActionBtnClicked()" > -
- - Name - - {{ "feature-flags.add-flag-modal.name-hint.text" | translate }} - + + + Name + + {{ 'feature-flags.add-flag-modal.name-hint.text' | translate }} + - - Key - - {{ "feature-flags.add-flag-modal.key-hint.text" | translate }}Learn more - + + Key + + {{ 'feature-flags.add-flag-modal.key-hint.text' | translate + }}Learn more + - - Description (optional) - - + + Description (optional) + + - - App Context - - {{ context }} - - {{ "feature-flags.add-flag-modal.app-context-hint.text" | translate }}Learn more - + + App Context + + {{ context }} + + {{ 'feature-flags.add-flag-modal.app-context-hint.text' | translate + }}Learn more + - - - Tags (optional) - - -
+ + diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts index 22e3da9f12..610bc299a1 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/add-feature-flag-modal/add-feature-flag-modal.component.ts @@ -1,5 +1,8 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; -import { CommonModalComponent } from '../../../../../shared-standalone-component-lib/components'; +import { + CommonModalComponent, + CommonTagsInputComponent, +} from '../../../../../shared-standalone-component-lib/components'; import { MAT_DIALOG_DATA, MatDialog, @@ -45,6 +48,7 @@ import { ExperimentService } from '../../../../../core/experiments/experiments.s MatIcon, ReactiveFormsModule, TranslateModule, + CommonTagsInputComponent, ], templateUrl: './add-feature-flag-modal.component.html', styleUrl: './add-feature-flag-modal.component.scss', @@ -81,7 +85,7 @@ export class AddFeatureFlagModalComponent { key: ['', Validators.required], description: [''], appContext: ['', Validators.required], - tags: [null], // this will need corrected, it should be an array of strings, for now we're hackin + tags: [], }); } @@ -111,7 +115,7 @@ export class AddFeatureFlagModalComponent { description, status: FEATURE_FLAG_STATUS.DISABLED, context: [appContext], - tags: tags?.split(',').map((tag: string) => tag.trim()) ?? [], // this will need corrected, it should be an array of strings, for now we're hackin + tags: tags, // it is now an array of strings featureFlagSegmentInclusion: { segment: { type: SEGMENT_TYPE.PRIVATE, diff --git a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.scss b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.scss index 009d6a4b89..dfa521fccb 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.scss +++ b/frontend/projects/upgrade/src/app/features/dashboard/feature-flags/modals/delete-feature-flag-modal/delete-feature-flag-modal.component.scss @@ -14,4 +14,4 @@ .confirm-delete { margin-bottom: 12px; -} \ No newline at end of file +} diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html new file mode 100644 index 0000000000..db7101a866 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.html @@ -0,0 +1,25 @@ + + + {{ 'home.new-experiment.overview.tags.placeHolder' | translate }} + + + + + {{ componentTag }} + + cancel + + + + diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss new file mode 100644 index 0000000000..c7acb4bf6e --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.scss @@ -0,0 +1,3 @@ +mat-form-field { + width: 100%; +} diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts new file mode 100644 index 0000000000..9eee4a7096 --- /dev/null +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-tag-input/common-tag-input.component.ts @@ -0,0 +1,84 @@ +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { ENTER, COMMA } from '@angular/cdk/keycodes'; +import { CommonModule } from '@angular/common'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { TranslateModule } from '@ngx-translate/core'; + +// This Component is made to manage a list of tags using mat-chips. +// It uses ControlValueAccessor which implements methods to synchronize the component's value with the parent form control. +// writeValue(value: string[]): Sets the component's value. +// registerOnChange(fn: any): Registers a callback for when the value changes. +// registerOnTouched(fn: any): Registers a callback for when the component is touched. + +// Example Usage: +// + +@Component({ + selector: 'app-common-tags-input', + templateUrl: './common-tag-input.component.html', + styleUrls: ['./common-tag-input.component.scss'], + standalone: true, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CommonTagsInputComponent), + multi: true, + }, + ], + imports: [CommonModule, MatChipsModule, MatFormFieldModule, MatIconModule, MatInputModule, TranslateModule], +}) +export class CommonTagsInputComponent implements ControlValueAccessor { + isChipSelectable = true; + isChipRemovable = true; + addChipOnBlur = true; + readonly separatorKeysCodes: number[] = [ENTER, COMMA]; + + tags = new FormControl([]); + + addChip(event: MatChipInputEvent) { + const input = event.chipInput; + const value = (event.value || '').trim().toLowerCase(); + + // Add chip + if (value) { + const currentTags = this.tags.value || []; + if (!currentTags.includes(value)) { + this.tags.setValue([...currentTags, value]); + this.tags.updateValueAndValidity(); + } + } + + // Reset the input value + if (input) { + input.clear(); + } + } + + removeChip(tag: string) { + const currentTags = this.tags.value || []; + const index = currentTags.indexOf(tag); + + if (index >= 0) { + currentTags.splice(index, 1); + this.tags.setValue(currentTags); + this.tags.updateValueAndValidity(); + } + } + + // Implement ControlValueAccessor methods + writeValue(value: string[]) { + this.tags.setValue(value || []); + } + + registerOnChange(fn: any) { + this.tags.valueChanges.subscribe(fn); + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + registerOnTouched(fn: any) {} +} diff --git a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.ts b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.ts index 194f0993a1..6309e34e8d 100644 --- a/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.ts +++ b/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.ts @@ -9,6 +9,7 @@ import { CommonModalComponent } from './common-modal/common-modal.component'; import { CommonStatusIndicatorChipComponent } from './common-status-indicator-chip/common-status-indicator-chip.component'; import { CommonSectionCardTitleHeaderComponent } from './common-section-card-title-header/common-section-card-title-header.component'; import { CommonSectionCardOverviewDetailsComponent } from './common-section-card-overview-details/common-section-card-overview-details.component'; +import { CommonTagsInputComponent } from './common-tag-input/common-tag-input.component'; export { CommonPageComponent, @@ -22,4 +23,5 @@ export { CommonSectionCardOverviewDetailsComponent, CommonModalComponent, CommonStatusIndicatorChipComponent, + CommonTagsInputComponent, };