@brickclay-org/ui 0.0.39 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/ASSETS_SETUP.md +59 -0
  2. package/ng-package.json +29 -0
  3. package/package.json +15 -26
  4. package/src/lib/assets/icons.ts +8 -0
  5. package/src/lib/badge/badge.html +24 -0
  6. package/src/lib/badge/badge.ts +42 -0
  7. package/src/lib/brickclay-lib.spec.ts +23 -0
  8. package/src/lib/brickclay-lib.ts +15 -0
  9. package/src/lib/button-group/button-group.html +12 -0
  10. package/src/lib/button-group/button-group.ts +73 -0
  11. package/src/lib/calender/calendar.module.ts +35 -0
  12. package/src/lib/calender/components/custom-calendar/custom-calendar.component.css +698 -0
  13. package/src/lib/calender/components/custom-calendar/custom-calendar.component.html +230 -0
  14. package/src/lib/calender/components/custom-calendar/custom-calendar.component.spec.ts +23 -0
  15. package/src/lib/calender/components/custom-calendar/custom-calendar.component.ts +1554 -0
  16. package/src/lib/calender/components/scheduled-date-picker/scheduled-date-picker.component.css +373 -0
  17. package/src/lib/calender/components/scheduled-date-picker/scheduled-date-picker.component.html +210 -0
  18. package/src/lib/calender/components/scheduled-date-picker/scheduled-date-picker.component.ts +361 -0
  19. package/src/lib/calender/components/time-picker/time-picker.component.css +174 -0
  20. package/src/lib/calender/components/time-picker/time-picker.component.html +60 -0
  21. package/src/lib/calender/components/time-picker/time-picker.component.ts +283 -0
  22. package/src/lib/calender/services/calendar-manager.service.ts +45 -0
  23. package/src/lib/checkbox/checkbox.html +42 -0
  24. package/src/lib/checkbox/checkbox.ts +67 -0
  25. package/src/lib/chips/chips.html +74 -0
  26. package/src/lib/chips/chips.ts +222 -0
  27. package/src/lib/grid/components/grid/grid.html +97 -0
  28. package/src/lib/grid/components/grid/grid.ts +139 -0
  29. package/src/lib/grid/models/grid.model.ts +20 -0
  30. package/src/lib/input/input.html +127 -0
  31. package/src/lib/input/input.ts +394 -0
  32. package/src/lib/pill/pill.html +24 -0
  33. package/src/lib/pill/pill.ts +39 -0
  34. package/src/lib/radio/radio.html +58 -0
  35. package/src/lib/radio/radio.ts +72 -0
  36. package/src/lib/select/select.html +111 -0
  37. package/src/lib/select/select.ts +401 -0
  38. package/src/lib/spinner/spinner.html +5 -0
  39. package/src/lib/spinner/spinner.ts +22 -0
  40. package/src/lib/tabs/tabs.html +28 -0
  41. package/src/lib/tabs/tabs.ts +48 -0
  42. package/src/lib/textarea/textarea.html +80 -0
  43. package/src/lib/textarea/textarea.ts +172 -0
  44. package/src/lib/toggle/toggle.html +24 -0
  45. package/src/lib/toggle/toggle.ts +62 -0
  46. package/src/lib/ui-button/ui-button.html +25 -0
  47. package/src/lib/ui-button/ui-button.ts +55 -0
  48. package/src/lib/ui-icon-button/ui-icon-button.html +7 -0
  49. package/src/lib/ui-icon-button/ui-icon-button.ts +38 -0
  50. package/src/public-api.ts +43 -0
  51. package/tsconfig.lib.json +19 -0
  52. package/tsconfig.lib.prod.json +11 -0
  53. package/tsconfig.spec.json +15 -0
  54. package/fesm2022/brickclay-org-ui.mjs +0 -4035
  55. package/fesm2022/brickclay-org-ui.mjs.map +0 -1
  56. package/index.d.ts +0 -857
@@ -0,0 +1,222 @@
1
+ import { Component, Input, forwardRef, ViewChild, ElementRef, AfterViewInit, EventEmitter, Output } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
4
+
5
+ @Component({
6
+ selector: 'bk-chips',
7
+ standalone: true,
8
+ imports: [CommonModule, FormsModule],
9
+ templateUrl: './chips.html',
10
+ styleUrl: './chips.css',
11
+ providers: [
12
+ {
13
+ provide: NG_VALUE_ACCESSOR,
14
+ useExisting: forwardRef(() => BkChips),
15
+ multi: true
16
+ }
17
+ ]
18
+ })
19
+ export class BkChips implements ControlValueAccessor, AfterViewInit {
20
+ @ViewChild('badgeInput') badgeInput!: ElementRef<HTMLInputElement>;
21
+ @ViewChild('fieldWrapper') fieldWrapper!: ElementRef<HTMLDivElement>;
22
+
23
+ // --- Configuration Inputs ---
24
+ @Input() id!: string ;
25
+ @Input() name!: string ;
26
+ @Input() label: string = '';
27
+ @Input() placeholder: string = '';
28
+ @Input() hint: string = '';
29
+ @Input() required: boolean = false;
30
+ @Input() disabled: boolean = false;
31
+ @Input() readOnly: boolean = false;
32
+ /**
33
+ * If true, displays the component in an error state (red border).
34
+ * It also replaces the hint text with the error message.
35
+ */
36
+ @Input() hasError: boolean = false;
37
+ @Input() errorMessage: string = 'This is a error message';
38
+ // =================== Output Emitter ===================
39
+ @Output() input = new EventEmitter<Event>();
40
+ @Output() change = new EventEmitter<string[]>();
41
+
42
+ @Output() focus = new EventEmitter<Event>();
43
+ @Output() blur = new EventEmitter<Event>();
44
+ // --- State Properties ---
45
+ badges: string[] = [];
46
+ inputValue: string = '';
47
+ isFocused: boolean = false;
48
+ needsScroll: boolean = false;
49
+
50
+ // --- ControlValueAccessor Methods ---
51
+ onChange: any = () => {};
52
+ onTouched: any = () => {};
53
+
54
+ // Get input state for styling variants
55
+ get inputState(): 'default' | 'focused' | 'filled' | 'error' | 'disabled' {
56
+ if (this.disabled) return 'disabled';
57
+ if (this.hasError) return 'error';
58
+ if (this.isFocused) return 'focused';
59
+ if (this.badges.length > 0 || this.inputValue.length > 0) return 'filled';
60
+ return 'default';
61
+ }
62
+
63
+
64
+
65
+ // Handle keydown events (Enter to add badge, Backspace to remove)
66
+ onKeyDown(event: KeyboardEvent): void {
67
+ if (this.disabled) return;
68
+
69
+ // Add badge on Enter or comma
70
+ if (event.key === 'Enter' || event.key === ',') {
71
+ event.preventDefault();
72
+ this.addBadge();
73
+ }
74
+ // Remove last badge on Backspace when input is empty
75
+ else if (event.key === 'Backspace' && this.inputValue === '' && this.badges.length > 0) {
76
+ this.removeBadge(this.badges.length - 1);
77
+ }
78
+ }
79
+
80
+ // Add a badge from the current input value
81
+ addBadge(): void {
82
+ const trimmedValue = this.inputValue.trim();
83
+ if (trimmedValue && !this.badges.includes(trimmedValue)) {
84
+ this.badges.push(trimmedValue);
85
+ this.inputValue = '';
86
+ this.updateValue();
87
+ // Check if scroll is needed after adding badge (with delay for DOM update)
88
+ setTimeout(() => this.checkScrollNeeded(), 10);
89
+ } else if (trimmedValue && this.badges.includes(trimmedValue)) {
90
+ // Optionally show a message that badge already exists
91
+ this.inputValue = '';
92
+ }
93
+ }
94
+
95
+ // Remove a badge at the given index
96
+ removeBadge(index: number): void {
97
+ if (this.disabled) return;
98
+ this.badges.splice(index, 1);
99
+ this.updateValue();
100
+ // Check if scroll is needed after removing badge (with delay for DOM update)
101
+ setTimeout(() => {
102
+ this.checkScrollNeeded();
103
+ if (this.badgeInput) {
104
+ this.badgeInput.nativeElement.focus();
105
+ }
106
+ }, 10);
107
+ }
108
+
109
+ // Check if scrolling is needed (when content wraps)
110
+ checkScrollNeeded(): void {
111
+ if (this.fieldWrapper && this.fieldWrapper.nativeElement) {
112
+ const wrapper = this.fieldWrapper.nativeElement;
113
+
114
+ // Get all badge items
115
+ const badgeItems = wrapper.querySelectorAll('.input-badge-item');
116
+
117
+ if (badgeItems.length === 0) {
118
+ // No badges, no scroll needed
119
+ this.needsScroll = false;
120
+ return;
121
+ }
122
+
123
+ // Get the first badge's top position
124
+ const firstBadge = badgeItems[0] as HTMLElement;
125
+ const firstBadgeTop = firstBadge.offsetTop;
126
+
127
+ // Check if any badge is on a different line (different top position)
128
+ let hasWrapped = false;
129
+ for (let i = 1; i < badgeItems.length; i++) {
130
+ const badge = badgeItems[i] as HTMLElement;
131
+ // If a badge's top position is different, it means it wrapped to a new line
132
+ if (Math.abs(badge.offsetTop - firstBadgeTop) > 5) {
133
+ hasWrapped = true;
134
+ break;
135
+ }
136
+ }
137
+
138
+ // Also check if the input field is on a different line
139
+ if (!hasWrapped && this.badgeInput && this.badgeInput.nativeElement) {
140
+ const inputTop = this.badgeInput.nativeElement.offsetTop;
141
+ if (Math.abs(inputTop - firstBadgeTop) > 5) {
142
+ hasWrapped = true;
143
+ }
144
+ }
145
+
146
+ // Only enable scroll if content actually wrapped
147
+ this.needsScroll = hasWrapped;
148
+ }
149
+ }
150
+
151
+ // Update the value and notify Angular Forms
152
+ updateValue(): void {
153
+ this.onChange([...this.badges]);
154
+ this.change.emit([...this.badges]);
155
+ }
156
+
157
+ // Focus the input field
158
+ focusInput(): void {
159
+ if (!this.disabled && this.badgeInput) {
160
+ this.badgeInput.nativeElement.focus();
161
+ }
162
+ }
163
+
164
+ ngAfterViewInit(): void {
165
+ // Check scroll after view initializes
166
+ setTimeout(() => this.checkScrollNeeded(), 10);
167
+ }
168
+
169
+ // Called when Angular writes a value TO the component (e.g. initial value)
170
+ writeValue(value: any): void {
171
+ if (Array.isArray(value)) {
172
+ this.badges = [...value];
173
+ } else {
174
+ this.badges = [];
175
+ }
176
+ this.inputValue = '';
177
+ // Check scroll after value is written (with delay for DOM update)
178
+ setTimeout(() => this.checkScrollNeeded(), 10);
179
+ }
180
+
181
+ // Register function to call when value changes
182
+ registerOnChange(fn: any): void {
183
+ this.onChange = fn;
184
+ }
185
+
186
+ // Register function to call when component is touched/blurred
187
+ registerOnTouched(fn: any): void {
188
+ this.onTouched = fn;
189
+ }
190
+
191
+ // Called when the component is disabled via the form control
192
+ setDisabledState(disabled: boolean): void {
193
+ this.disabled = disabled;
194
+ }
195
+
196
+ // =================== Event Handlers ===================
197
+
198
+ // Called when the value in the UI changes (user types)
199
+ handleInput(event: Event): void {
200
+ const input = event.target as HTMLInputElement;
201
+ this.inputValue = input.value;
202
+ }
203
+
204
+
205
+ handleFocus(event: Event): void {
206
+ if (!this.disabled) {
207
+ this.isFocused = true;
208
+ }
209
+ this.onChange([...this.badges]);
210
+ this.focus.emit(event);
211
+ }
212
+
213
+ handleBlur(event:Event): void {
214
+ this.isFocused = false;
215
+ this.onTouched();
216
+ this.onChange([...this.badges]);
217
+ this.blur.emit(event);
218
+ }
219
+
220
+
221
+
222
+ }
@@ -0,0 +1,97 @@
1
+ <div
2
+ #tableScrollContainer
3
+ cdkScrollable
4
+ class="h-[calc(100vh-260px)] overflow-y-auto">
5
+
6
+ <table class="min-w-full text-sm text-left text-gray-800 table-auto border-collapse">
7
+
8
+ <!-- ================= HEADER ================= -->
9
+ <thead>
10
+ <tr>
11
+ @for (col of columns; track col.header;let i=$index;) {
12
+ @if (isColumnVisible(col)) {
13
+ <th
14
+ class="grid-header sticky top-0 cursor-pointer"
15
+ [class.action-sticky]="col.sticky"
16
+ [class.z-10]="col.sticky"
17
+ [ngClass]="col.headerClass"
18
+ [ngClass]="col.cellClass"
19
+ (click)="sort(col,i)"
20
+ >
21
+ <span class="flex items-center gap-1"
22
+ [ngClass]="sortColumn === col.field
23
+ ? (sortDirection === 'asc' ? 'grid-asc' : 'grid-desc')
24
+ : ''">
25
+ {{ col.header }}
26
+ @if (col.sortable) {
27
+ <span class="grid-sort-icon"></span>
28
+ }
29
+ </span>
30
+ </th>
31
+ }
32
+ }
33
+
34
+ @if (actions.length) {
35
+ <th class="grid-header sticky top-0 action-sticky z-10 !bg-[#FBFBFC] w-20">
36
+ Action
37
+ </th>
38
+ }
39
+ </tr>
40
+ </thead>
41
+
42
+ <!-- ================= BODY ================= -->
43
+ <tbody
44
+ cdkDropList
45
+ [cdkDropListDisabled]="!draggable"
46
+ [cdkDropListData]="result || []"
47
+ (cdkDropListDropped)="dropList($event)">
48
+
49
+ @for (row of result; track row; let rowIndex = $index) {
50
+ <tr
51
+ cdkDrag
52
+ cdkDragLockAxis="y"
53
+ [cdkDragDisabled]="!draggable"
54
+ (cdkDragStarted)="onDragStart($event)"
55
+ (cdkDragMoved)="onDragMoved($event)"
56
+ class="" [ngClass]="{ 'cursor-move ': draggable }">
57
+
58
+ @for (col of columns; track col.header; let colIndex = $index) {
59
+ @if (isColumnVisible(col)) {
60
+ <td class="grid-cell text-nowrap" [ngClass]="col.cellClass">
61
+ @if (draggable && colIndex === firstVisibleColumnIndex) {
62
+ <span cdkDragHandle class="mr-2 text-gray-400" [ngClass]="{ 'cursor-move': draggable }">☰</span>
63
+ }
64
+ {{ getCellValue(row, col) }}
65
+ </td>
66
+ }
67
+ }
68
+
69
+ @if (actions.length) {
70
+ <td class="grid-cell action-sticky text-center">
71
+ <div class="flex items-center justify-center gap-1.5">
72
+ @for (action of actions; track action.name) {
73
+ @if (action.hasPermission) {
74
+ <!-- appTooltip="{{ action.tooltip }}"
75
+ appTooltipPosition="top" -->
76
+ <button
77
+ class="size-6 flex items-center justify-center rounded hover:bg-[#F8F8FA]"
78
+ (click)="emitAction(action, row)"
79
+
80
+ >
81
+ <img
82
+ [src]="action.icon"
83
+ width="14"
84
+ height="14"
85
+ alt="action-icon"
86
+ />
87
+ </button>
88
+ }
89
+ }
90
+ </div>
91
+ </td>
92
+ }
93
+ </tr>
94
+ }
95
+ </tbody>
96
+ </table>
97
+ </div>
@@ -0,0 +1,139 @@
1
+ import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
2
+ import { TableColumn, TableAction } from '../../models/grid.model';
3
+ import { CdkDragDrop, moveItemInArray, CdkDragMove, CdkDragStart, DragDropModule } from '@angular/cdk/drag-drop';
4
+ import { CommonModule } from '@angular/common';
5
+ import { ScrollingModule } from '@angular/cdk/scrolling';
6
+ export type SortDirection = 'asc' | 'desc';
7
+ @Component({
8
+ selector: 'bk-grid',
9
+ standalone: true,
10
+ imports: [CommonModule,DragDropModule,ScrollingModule],
11
+ templateUrl: './grid.html',
12
+ styleUrl: './grid.css',
13
+ })
14
+ export class BkGrid<T = any> {
15
+ @Input() draggable: boolean = false;
16
+ @Input() columns: TableColumn<T>[] = [];
17
+ @Input() result!: T[];
18
+ @Input() actions: TableAction[] = [];
19
+
20
+ @Output() actionClick = new EventEmitter<{
21
+ action: string;
22
+ row: T;
23
+ }>();
24
+
25
+ @Output() sortChange = new EventEmitter<{
26
+ columnIndex: number;
27
+ column: TableColumn<T>;
28
+ direction: SortDirection;
29
+ }>();
30
+ @Output() dragDropChange = new EventEmitter<T[]>();
31
+ sortColumn?: keyof T;
32
+ sortDirection:SortDirection = 'asc';
33
+
34
+ @ViewChild('tableScrollContainer', { static: false })
35
+ tableScrollContainer!: ElementRef<HTMLDivElement>;
36
+
37
+ get firstVisibleColumnIndex(): number {
38
+ const index = this.columns.findIndex(col => col.visible !== false);
39
+ return index >= 0 ? index : 0;
40
+ }
41
+ /* ---------- Sorting ---------- */
42
+ sort(column: TableColumn<T>, index: number) {
43
+ if (!column.sortable || !column.field) return;
44
+
45
+ // Toggle sort direction
46
+ this.sortDirection =
47
+ this.sortColumn === column.field ? (this.sortDirection === 'asc' ? 'desc' : 'asc') : 'asc';
48
+
49
+ this.sortColumn = column.field;
50
+
51
+ // Emit sort change separately
52
+ this.sortChange.emit({
53
+ columnIndex: index,
54
+ column,
55
+ direction: this.sortDirection,
56
+ });
57
+ }
58
+
59
+ /* ---------- Visibility ---------- */
60
+ isColumnVisible(column: TableColumn<T>): boolean {
61
+ return column.visible !== false;
62
+ }
63
+
64
+ /* ---------- Cell Value ---------- */
65
+ getCellValue(row: T, column: TableColumn<T>): string {
66
+ if (column.formatter) {
67
+ return column.formatter(row);
68
+ }
69
+ if (column.field) {
70
+ return String(row[column.field] ?? '');
71
+ }
72
+ return '';
73
+ }
74
+
75
+ /* ---------- Actions ---------- */
76
+ emitAction(action: TableAction, row: T) {
77
+ this.actionClick.emit({
78
+ action: action.name,
79
+ row,
80
+ });
81
+ }
82
+
83
+ dropList(event: CdkDragDrop<T[]>) {
84
+ if (!this.draggable || !this.result) return;
85
+
86
+ moveItemInArray(this.result, event.previousIndex, event.currentIndex);
87
+
88
+ // Update existing sortOrder on T
89
+ this.result.forEach((item: any, index) => {
90
+ item.sortOrder = index + 1;
91
+ });
92
+
93
+ // Emit reordered list
94
+ this.dragDropChange.emit(this.result);
95
+ }
96
+
97
+ onDragMoved(event: CdkDragMove<any>) {
98
+ if (!this.tableScrollContainer) return;
99
+
100
+ const container = this.tableScrollContainer.nativeElement;
101
+ const rect = container.getBoundingClientRect();
102
+ const pointerY = event.pointerPosition.y - rect.top;
103
+
104
+ const threshold = 80;
105
+ const maxSpeed = 25;
106
+
107
+ if (pointerY < threshold) {
108
+ const intensity = 1 - pointerY / threshold;
109
+ container.scrollTop -= Math.min(maxSpeed, intensity * maxSpeed);
110
+ } else if (pointerY > rect.height - threshold) {
111
+ const intensity = 1 - (rect.height - pointerY) / threshold;
112
+ container.scrollTop += Math.min(maxSpeed, intensity * maxSpeed);
113
+ }
114
+ }
115
+
116
+ onDragStart(event: CdkDragStart<any>) {
117
+ const row = event.source.element.nativeElement as HTMLElement;
118
+ const cells = Array.from(row.querySelectorAll('td'));
119
+
120
+ setTimeout(() => {
121
+ const preview = document.querySelector('.cdk-drag-preview') as HTMLElement;
122
+
123
+ if (!preview) return;
124
+
125
+ const previewCells = preview.querySelectorAll('td');
126
+
127
+ cells.forEach((cell, index) => {
128
+ const width = cell.getBoundingClientRect().width + 'px';
129
+ const previewCell = previewCells[index] as HTMLElement;
130
+
131
+ if (previewCell) {
132
+ previewCell.style.width = width;
133
+ previewCell.style.minWidth = width;
134
+ previewCell.style.maxWidth = width;
135
+ }
136
+ });
137
+ });
138
+ }
139
+ }
@@ -0,0 +1,20 @@
1
+ export interface TableColumn<T = any> {
2
+ header: string;
3
+ field?: keyof T;
4
+ width?: string;
5
+ sticky?: boolean;
6
+ sortable?: boolean;
7
+ headerClass?: string;
8
+ cellClass?: string;
9
+ formatter?: (row: T) => string;
10
+
11
+ /** show / hide both th + td */
12
+ visible?: boolean; // default: true
13
+ }
14
+
15
+ export interface TableAction{
16
+ name: string; // e.g. edit, delete
17
+ icon: string;
18
+ tooltip: string;
19
+ hasPermission: boolean;
20
+ }
@@ -0,0 +1,127 @@
1
+ <div class="input-container">
2
+ @if(label){
3
+ <label [for]="id" class="input-label">
4
+ {{ label }}
5
+ @if(required){
6
+ <span class="input-label-required">*</span>
7
+ }
8
+ </label>
9
+ }
10
+
11
+ <div class="input-wrapper" [ngClass]="{
12
+ 'input-wrapper--url': type === 'url',
13
+ 'input-wrapper--phone': phone,
14
+ 'input-wrapper--password': password,
15
+ 'input-wrapper--icon': iconSrc && showIcon
16
+ }">
17
+
18
+ <input
19
+ #inputField
20
+ [type]="currentInputType"
21
+ [id]="id"
22
+ [name]="name"
23
+ [disabled]="disabled"
24
+ [tabindex]="tabIndex"
25
+ [readOnly]="readOnly"
26
+ [attr.maxlength]="maxlength"
27
+ [attr.minlength]="minlength"
28
+ [autocomplete]="autoComplete"
29
+ [autocapitalize]="autoCapitalize"
30
+
31
+ [attr.max]="max"
32
+ [attr.min]="min"
33
+ [attr.step]="step"
34
+
35
+ [placeholder]="placeHolderText"
36
+ [pattern]="pattern"
37
+ [autocomplete]="autoComplete"
38
+ [value]="inputValue"
39
+
40
+
41
+
42
+ [mask]="maskValue"
43
+ [prefix]="maskPrefixValue"
44
+ [showMaskTyped]="false"
45
+ [dropSpecialCharacters]="false"
46
+ (change)="handleChange($event)"
47
+ (input)="handleInput($event)"
48
+ (focus)="handleFocus($event)"
49
+ (blur)="handleBlur($event)"
50
+ class="input-field"
51
+
52
+ [ngClass]="{
53
+ 'input-field--url': type === 'url',
54
+ 'input-field--phone': phone,
55
+ 'input-field--icon-left': iconSrc && showIcon && iconOrientation === 'left',
56
+ 'input-field--icon-right': iconSrc && showIcon && iconOrientation === 'right',
57
+ 'input-field--password': password,
58
+ 'input-field--default': inputState === 'default',
59
+ 'input-field--focused': inputState === 'focused',
60
+ 'input-field--filled': inputState === 'filled',
61
+ 'input-field--error': inputState === 'error',
62
+ 'input-field--disabled': inputState === 'disabled'
63
+ }">
64
+
65
+ @if(iconSrc && showIcon){
66
+ <img (click)="handleIconClick($event)" [src]="iconSrc" [alt]="iconAlt" [ngClass]="{
67
+ 'input-search-icon--left': iconOrientation === 'left',
68
+ 'input-search-icon--right': iconOrientation === 'right',
69
+ 'cursor-pointer': !disabled && !readOnly
70
+ }" class="input-search-icon">
71
+ }
72
+
73
+ @if(showErrorIcon){
74
+ <img src="../../assets/images/icons/global/info-circle.svg" class="input-search-icon input-search-icon--right">
75
+ }
76
+
77
+ @if(password){
78
+ <button type="button" (click)="togglePasswordVisibility($event)" class="input-password-toggle" [disabled]="disabled" tabindex="-1">
79
+ <img [src]="showPassword ? '../../assets/images/icons/global/eye-slash-icon.svg' : '../../assets/images/icons/global/eye-icon.svg'" [alt]="showPassword ? 'Hide password' : 'Show password'" class="input-password-icon">
80
+ </button>
81
+ }
82
+
83
+ @if(phone){
84
+ <div #selectRef class="input-phone-selector" [ngClass]="{
85
+ 'input-phone-selector--default': inputState === 'default',
86
+ 'input-phone-selector--focused': inputState === 'focused',
87
+ 'input-phone-selector--filled': inputState === 'filled',
88
+ 'input-phone-selector--error': inputState === 'error',
89
+ 'input-phone-selector--disabled': inputState === 'disabled'
90
+ }" (click)="toggleDropdown($event)">
91
+ <span class="input-phone-selector-text">{{ selectedCountry.name }}</span>
92
+ <img src="../../assets/images/icons/global/input-arrow-down.svg" alt="Dropdown" class="input-phone-selector-arrow" [ngClass]="{'input-phone-selector-arrow--open': isDropdownOpen}">
93
+ </div>
94
+ }
95
+
96
+ @if(phone && isDropdownOpen){
97
+ <div #dropdownRef class="input-phone-dropdown" (click)="$event.stopPropagation()">
98
+ <button *ngFor="let country of countryOptions" type="button" class="input-phone-dropdown-item" [ngClass]="{'input-phone-dropdown-item--active': selectedCountry.code === country.code}" (click)="selectCountry(country); $event.stopPropagation()">
99
+ {{ country.name }}
100
+ </button>
101
+ </div>
102
+ }
103
+
104
+
105
+
106
+ @if(type === 'url'){
107
+ <span class="input-url-prefix" [ngClass]="{
108
+ 'input-url-prefix--default': inputState === 'default',
109
+ 'input-url-prefix--focused': inputState === 'focused',
110
+ 'input-url-prefix--filled': inputState === 'filled',
111
+ 'input-url-prefix--error': inputState === 'error',
112
+ 'input-url-prefix--disabled': inputState === 'disabled'
113
+ }">https</span>
114
+ }
115
+ </div>
116
+
117
+ @if(hasError){
118
+ @if (errorMessage) {
119
+ <p class="input-error">{{ errorMessage }}</p>
120
+ }
121
+ }
122
+ @if(!hasError){
123
+ @if(hint){
124
+ <p class="input-hint">{{ hint }}</p>
125
+ }
126
+ }
127
+ </div>