crdx-components 1.0.0
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.
- package/.github/workflows/publish.yml +38 -0
- package/bun.lock +491 -0
- package/crdx-components-1.0.0.tgz +0 -0
- package/crdx-components-tokenized-components-1.0.1.tgz +0 -0
- package/ng-package.json +12 -0
- package/npm +0 -0
- package/package.json +33 -0
- package/src/index.ts +45 -0
- package/src/lib/components/breadcrumb/breadcrumb.component.css +206 -0
- package/src/lib/components/breadcrumb/breadcrumb.component.html +15 -0
- package/src/lib/components/breadcrumb/breadcrumb.component.ts +47 -0
- package/src/lib/components/button/button.css +371 -0
- package/src/lib/components/button/button.html +187 -0
- package/src/lib/components/button/button.ts +103 -0
- package/src/lib/components/card/card.css +285 -0
- package/src/lib/components/card/card.html +69 -0
- package/src/lib/components/card/card.ts +93 -0
- package/src/lib/components/checkbox/checkbox-showcase.component.css +42 -0
- package/src/lib/components/checkbox/checkbox-showcase.component.html +36 -0
- package/src/lib/components/checkbox/checkbox-showcase.component.ts +13 -0
- package/src/lib/components/checkbox/checkbox.css +10 -0
- package/src/lib/components/checkbox/checkbox.html +13 -0
- package/src/lib/components/checkbox/checkbox.ts +64 -0
- package/src/lib/components/circular-progress-stepper/circular-progress-stepper.css +89 -0
- package/src/lib/components/circular-progress-stepper/circular-progress-stepper.html +23 -0
- package/src/lib/components/circular-progress-stepper/circular-progress-stepper.ts +40 -0
- package/src/lib/components/dialogs/alert-modal/alert-modal.css +118 -0
- package/src/lib/components/dialogs/alert-modal/alert-modal.html +29 -0
- package/src/lib/components/dialogs/alert-modal/alert-modal.ts +28 -0
- package/src/lib/components/dialogs/confirm-modal/confirm-modal.css +219 -0
- package/src/lib/components/dialogs/confirm-modal/confirm-modal.html +60 -0
- package/src/lib/components/dialogs/confirm-modal/confirm-modal.store.ts +139 -0
- package/src/lib/components/dialogs/confirm-modal/confirm-modal.ts +63 -0
- package/src/lib/components/dialogs/container-custom/container-custom.css +11 -0
- package/src/lib/components/dialogs/container-custom/container-custom.html +3 -0
- package/src/lib/components/dialogs/container-custom/container-custom.ts +37 -0
- package/src/lib/components/dialogs/container-custom/custom-modal.state.ts +57 -0
- package/src/lib/components/dialogs/error-modal/error-modal.css +53 -0
- package/src/lib/components/dialogs/error-modal/error-modal.html +17 -0
- package/src/lib/components/dialogs/error-modal/error-modal.ts +20 -0
- package/src/lib/components/dialogs/side-modal/side-modal.css +80 -0
- package/src/lib/components/dialogs/side-modal/side-modal.html +30 -0
- package/src/lib/components/dialogs/side-modal/side-modal.state.ts +78 -0
- package/src/lib/components/dialogs/side-modal/side-modal.ts +50 -0
- package/src/lib/components/divider/divider.css +24 -0
- package/src/lib/components/divider/divider.html +7 -0
- package/src/lib/components/divider/divider.ts +13 -0
- package/src/lib/components/footer-actions/footer/footer-flow.store.ts +30 -0
- package/src/lib/components/footer-actions/footer/footer.html +14 -0
- package/src/lib/components/footer-actions/footer/footer.ts +50 -0
- package/src/lib/components/footer-actions/modal-footer-actions/modal-footer-actions.css +44 -0
- package/src/lib/components/footer-actions/modal-footer-actions/modal-footer-actions.html +7 -0
- package/src/lib/components/footer-actions/modal-footer-actions/modal-footer-actions.ts +12 -0
- package/src/lib/components/footer-actions/page-footer-actions/page-footer-actions.css +31 -0
- package/src/lib/components/footer-actions/page-footer-actions/page-footer-actions.html +7 -0
- package/src/lib/components/footer-actions/page-footer-actions/page-footer-actions.ts +12 -0
- package/src/lib/components/form-field/select-field.css +178 -0
- package/src/lib/components/form-field/select-field.html +94 -0
- package/src/lib/components/form-field/select-field.ts +324 -0
- package/src/lib/components/form-field/text-field.css +41 -0
- package/src/lib/components/form-field/text-field.html +38 -0
- package/src/lib/components/form-field/text-field.ts +102 -0
- package/src/lib/components/header/header.css +142 -0
- package/src/lib/components/header/header.html +36 -0
- package/src/lib/components/header/header.ts +101 -0
- package/src/lib/components/icon-button/icon-button.css +445 -0
- package/src/lib/components/icon-button/icon-button.html +15 -0
- package/src/lib/components/icon-button/icon-button.ts +49 -0
- package/src/lib/components/list-item/list-item.css +122 -0
- package/src/lib/components/list-item/list-item.html +79 -0
- package/src/lib/components/list-item/list-item.ts +104 -0
- package/src/lib/components/menu/menu.css +39 -0
- package/src/lib/components/menu/menu.html +57 -0
- package/src/lib/components/menu/menu.ts +159 -0
- package/src/lib/components/shared-table/shared-table-cell-template.directive.ts +25 -0
- package/src/lib/components/shared-table/shared-table.component.css +223 -0
- package/src/lib/components/shared-table/shared-table.component.html +96 -0
- package/src/lib/components/shared-table/shared-table.component.ts +172 -0
- package/src/lib/components/sidebar/sidebar.css +234 -0
- package/src/lib/components/sidebar/sidebar.html +67 -0
- package/src/lib/components/sidebar/sidebar.ts +92 -0
- package/src/lib/components/slide-toggle/slide-toggle.css +0 -0
- package/src/lib/components/slide-toggle/slide-toggle.html +3 -0
- package/src/lib/components/slide-toggle/slide-toggle.ts +18 -0
- package/src/lib/components/spinner/spinner.css +9 -0
- package/src/lib/components/spinner/spinner.html +9 -0
- package/src/lib/components/spinner/spinner.ts +17 -0
- package/src/lib/components/tooltip/tooltip.css +32 -0
- package/src/lib/components/tooltip/tooltip.html +3 -0
- package/src/lib/components/tooltip/tooltip.ts +31 -0
- package/src/lib/icons/configuration-countable.svg +8 -0
- package/src/lib/icons/edit-table.svg +3 -0
- package/src/lib/icons/edit.svg +3 -0
- package/src/lib/icons/error-circle.svg +8 -0
- package/src/lib/icons/hub.svg +3 -0
- package/src/lib/icons/icon-menu.svg +8 -0
- package/src/lib/icons/info-error.svg +8 -0
- package/src/lib/icons/keyboard_arrow_down.svg +1 -0
- package/src/lib/icons/logo.svg +0 -0
- package/src/lib/icons/logout.svg +0 -0
- package/src/lib/icons/notifications.svg +0 -0
- package/src/lib/icons/profile-user-menu.svg +0 -0
- package/src/lib/icons/profile.svg +0 -0
- package/src/lib/icons/register-icons.ts +101 -0
- package/src/lib/icons/visibility.svg +0 -0
- package/src/lib/lib-ui/lib-ui.html +1 -0
- package/src/lib/lib-ui/lib-ui.scss +0 -0
- package/src/lib/lib-ui/lib-ui.ts +9 -0
- package/src/lib/styles/generated/_app-tokens.scss +2757 -0
- package/src/lib/styles/generated/_md3-tokens.scss +179 -0
- package/src/lib/styles/generated/_tokens-avatars.scss +4 -0
- package/src/lib/styles/global-material-theme.scss +69 -0
- package/src/lib/styles/index.scss +16 -0
- package/src/lib/styles/layout.scss +29 -0
- package/src/lib/styles/overrides/_index.scss +11 -0
- package/src/lib/styles/overrides/_mat-button-overrides.scss +105 -0
- package/src/lib/styles/overrides/_mat-checkbox-overrides.scss +49 -0
- package/src/lib/styles/overrides/_mat-form-field-overrides.scss +148 -0
- package/src/lib/styles/overrides/_mat-icon-button-overrides.scss +20 -0
- package/src/lib/styles/overrides/_mat-list-overrides.scss +59 -0
- package/src/lib/styles/overrides/_mat-slide-toggle-overrides.scss +33 -0
- package/src/lib/styles/overrides/_mat-table-overrides.scss +259 -0
- package/src/lib/styles/overrides/_mat-tabs-overrides.scss +116 -0
- package/src/lib/styles/scrollbar.scss +40 -0
- package/src/lib/styles/text-classes.scss +116 -0
- package/src/lib/styles/typography.scss +14 -0
- package/tsconfig.json +30 -0
- package/tsconfig.lib.json +20 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
ChangeDetectorRef,
|
|
4
|
+
Component,
|
|
5
|
+
computed,
|
|
6
|
+
DestroyRef,
|
|
7
|
+
forwardRef,
|
|
8
|
+
inject,
|
|
9
|
+
Injector,
|
|
10
|
+
input,
|
|
11
|
+
OnInit,
|
|
12
|
+
output,
|
|
13
|
+
signal,
|
|
14
|
+
} from '@angular/core';
|
|
15
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
16
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
|
|
17
|
+
import { MatFormFieldAppearance, MatFormFieldModule } from '@angular/material/form-field';
|
|
18
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
19
|
+
import { MatInputModule } from '@angular/material/input';
|
|
20
|
+
import { MatSelectModule, MatSelectChange } from '@angular/material/select';
|
|
21
|
+
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
22
|
+
import { LibCheckboxComponent } from '../checkbox/checkbox';
|
|
23
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
24
|
+
|
|
25
|
+
export interface LibSelectOption<T = string> {
|
|
26
|
+
value: T;
|
|
27
|
+
label: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type VirtualRow<T> =
|
|
31
|
+
| { kind: 'selectAll' }
|
|
32
|
+
| { kind: 'option'; option: LibSelectOption<T> };
|
|
33
|
+
|
|
34
|
+
export type LibSelectMode = 'single' | 'multiple';
|
|
35
|
+
|
|
36
|
+
@Component({
|
|
37
|
+
selector: 'lib-select-field',
|
|
38
|
+
standalone: true,
|
|
39
|
+
imports: [MatFormFieldModule, MatSelectModule, ScrollingModule, MatInputModule, MatIconModule, LibCheckboxComponent],
|
|
40
|
+
templateUrl: './select-field.html',
|
|
41
|
+
styleUrl: './select-field.css',
|
|
42
|
+
providers: [
|
|
43
|
+
{
|
|
44
|
+
provide: NG_VALUE_ACCESSOR,
|
|
45
|
+
useExisting: forwardRef(() => LibSelectFieldComponent),
|
|
46
|
+
multi: true,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
|
+
})
|
|
51
|
+
export class LibSelectFieldComponent<T = string> implements ControlValueAccessor, OnInit {
|
|
52
|
+
private static readonly PANEL_CONTENT_HEIGHT = 240;
|
|
53
|
+
private static readonly FILTER_ROW_HEIGHT = 48;
|
|
54
|
+
private static readonly OPTION_ROW_HEIGHT = 48;
|
|
55
|
+
|
|
56
|
+
private readonly cdr = inject(ChangeDetectorRef);
|
|
57
|
+
private readonly destroyRef = inject(DestroyRef);
|
|
58
|
+
private readonly injector = inject(Injector);
|
|
59
|
+
private ngControl: NgControl | null = null;
|
|
60
|
+
|
|
61
|
+
readonly label = input('');
|
|
62
|
+
readonly appearance = input<'outline' | 'filled'>('outline');
|
|
63
|
+
readonly placeholder = input('Elegir opción');
|
|
64
|
+
readonly options = input<LibSelectOption<T>[]>([]);
|
|
65
|
+
readonly mode = input<LibSelectMode>('single');
|
|
66
|
+
readonly filterable = input(true, { transform: coerceBooleanProperty });
|
|
67
|
+
readonly filterPlaceholder = input('Buscar...');
|
|
68
|
+
readonly disabled = input(false, { transform: coerceBooleanProperty });
|
|
69
|
+
readonly required = input(false, { transform: coerceBooleanProperty });
|
|
70
|
+
readonly width = input<string | number | null>(null);
|
|
71
|
+
readonly selectAllLabel = input('Seleccionar todos');
|
|
72
|
+
readonly widthStyle = computed(() => {
|
|
73
|
+
const v = this.width();
|
|
74
|
+
return v == null || v === '' ? null : typeof v === 'number' ? `${v}px` : String(v);
|
|
75
|
+
});
|
|
76
|
+
readonly matAppearance = computed<MatFormFieldAppearance>(() =>
|
|
77
|
+
this.appearance() === 'filled' ? 'fill' : 'outline'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
readonly valueChange = output<T | T[]>();
|
|
81
|
+
|
|
82
|
+
readonly panelOpen = signal(false);
|
|
83
|
+
readonly filterTerm = signal('');
|
|
84
|
+
readonly disabledByControl = signal(false);
|
|
85
|
+
|
|
86
|
+
readonly isMultiple = computed(() => this.mode() === 'multiple');
|
|
87
|
+
readonly virtualViewportMaxHeight = computed(() =>
|
|
88
|
+
this.filterable()
|
|
89
|
+
? LibSelectFieldComponent.PANEL_CONTENT_HEIGHT - LibSelectFieldComponent.FILTER_ROW_HEIGHT
|
|
90
|
+
: LibSelectFieldComponent.PANEL_CONTENT_HEIGHT
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
readonly filteredOptions = computed(() => {
|
|
94
|
+
const opts = this.options();
|
|
95
|
+
const term = this.filterTerm().trim().toLowerCase();
|
|
96
|
+
if (!term) return opts;
|
|
97
|
+
return opts.filter(
|
|
98
|
+
(o) => o.label.toLowerCase().includes(term)
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
readonly virtualRows = computed<VirtualRow<T>[]>(() => {
|
|
103
|
+
const rows: VirtualRow<T>[] = this.filteredOptions().map((option) => ({
|
|
104
|
+
kind: 'option',
|
|
105
|
+
option,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
if (this.isMultiple()) {
|
|
109
|
+
rows.unshift({ kind: 'selectAll' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return rows;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
readonly virtualViewportHeight = computed(() => {
|
|
116
|
+
const rowsCount = this.virtualRows().length;
|
|
117
|
+
const contentHeight = rowsCount * LibSelectFieldComponent.OPTION_ROW_HEIGHT;
|
|
118
|
+
const maxHeight = this.virtualViewportMaxHeight();
|
|
119
|
+
const minHeight = rowsCount > 0 ? LibSelectFieldComponent.OPTION_ROW_HEIGHT : 0;
|
|
120
|
+
|
|
121
|
+
return Math.max(minHeight, Math.min(contentHeight, maxHeight));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
readonly allSelected = computed(() => {
|
|
125
|
+
if (!this.isMultiple()) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const opts = this.options();
|
|
129
|
+
const current = this.value();
|
|
130
|
+
if (!Array.isArray(current) || !opts.length) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const values = opts.map((o) => o.value);
|
|
134
|
+
return values.every((v) => current.includes(v));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
readonly selectedLabels = computed(() => {
|
|
138
|
+
const current = this.value();
|
|
139
|
+
if (!Array.isArray(current) || !current.length) {
|
|
140
|
+
return [] as string[];
|
|
141
|
+
}
|
|
142
|
+
const selectedSet = new Set(current);
|
|
143
|
+
return this.options()
|
|
144
|
+
.filter((o) => selectedSet.has(o.value))
|
|
145
|
+
.map((o) => o.label);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
readonly multiTriggerLabel = computed(() => {
|
|
149
|
+
const labels = this.selectedLabels();
|
|
150
|
+
if (!labels.length) {
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
if (labels.length === 1) {
|
|
154
|
+
return labels[0];
|
|
155
|
+
}
|
|
156
|
+
return `${labels[0]} (+${labels.length - 1} items)`;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
readonly selectAllIndeterminate = computed(() => {
|
|
160
|
+
if (!this.isMultiple()) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const opts = this.options();
|
|
164
|
+
const current = this.value();
|
|
165
|
+
if (!Array.isArray(current) || !opts.length) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const selectedCount = opts.filter((o) => current.includes(o.value)).length;
|
|
169
|
+
return selectedCount > 0 && selectedCount < opts.length;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
private readonly value = signal<T | T[] | null>(null);
|
|
173
|
+
private onChange: (v: T | T[] | null) => void = () => {
|
|
174
|
+
/* assigned by registerOnChange */
|
|
175
|
+
};
|
|
176
|
+
private onTouched: () => void = () => {
|
|
177
|
+
/* assigned by registerOnTouched */
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
ngOnInit(): void {
|
|
181
|
+
this.ngControl = this.injector.get(NgControl, null);
|
|
182
|
+
if (this.ngControl) {
|
|
183
|
+
this.ngControl.valueAccessor = this;
|
|
184
|
+
}
|
|
185
|
+
if (this.ngControl?.valueChanges) {
|
|
186
|
+
this.ngControl.valueChanges
|
|
187
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
188
|
+
.subscribe(() => this.cdr.markForCheck());
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
onPanelOpenChange(open: boolean): void {
|
|
193
|
+
this.panelOpen.set(open);
|
|
194
|
+
if (!open) this.filterTerm.set('');
|
|
195
|
+
this.cdr.markForCheck();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
onFilterInput(event: Event): void {
|
|
199
|
+
const el = event.target as HTMLInputElement;
|
|
200
|
+
this.filterTerm.set(el?.value ?? '');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
onFilterKeydown(event: KeyboardEvent): void {
|
|
204
|
+
event.stopPropagation();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
onSelectionChange(change: MatSelectChange): void {
|
|
208
|
+
if (this.isMultiple()) {
|
|
209
|
+
const incoming = Array.isArray(change.value) ? (change.value as T[]) : [];
|
|
210
|
+
const options = this.options().map((o) => o.value);
|
|
211
|
+
const optionSet = new Set(options);
|
|
212
|
+
const hasUnknownValues = incoming.some((v) => !optionSet.has(v));
|
|
213
|
+
|
|
214
|
+
// Ignore synthetic values emitted by the auxiliary "select all" option.
|
|
215
|
+
if (hasUnknownValues) {
|
|
216
|
+
this.cdr.markForCheck();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const incomingSet = new Set(incoming);
|
|
221
|
+
const normalized = options.filter((v) => incomingSet.has(v)) as T[];
|
|
222
|
+
this.value.set(normalized);
|
|
223
|
+
this.onChange(normalized);
|
|
224
|
+
this.valueChange.emit(normalized);
|
|
225
|
+
this.cdr.markForCheck();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.value.set(change.value);
|
|
230
|
+
this.onChange(change.value);
|
|
231
|
+
this.valueChange.emit(change.value);
|
|
232
|
+
this.cdr.markForCheck();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
onToggleSelectAll(checked: boolean): void {
|
|
236
|
+
if (!this.isMultiple()) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const options = this.options();
|
|
240
|
+
const currentValue = this.value();
|
|
241
|
+
const current = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
242
|
+
if (checked) {
|
|
243
|
+
const toAdd = options.map((o) => o.value);
|
|
244
|
+
const set = new Set(current);
|
|
245
|
+
for (const v of toAdd) {
|
|
246
|
+
set.add(v);
|
|
247
|
+
}
|
|
248
|
+
this.value.set(Array.from(set) as T[]);
|
|
249
|
+
} else {
|
|
250
|
+
const toRemove = new Set(options.map((o) => o.value));
|
|
251
|
+
this.value.set(current.filter((v) => !toRemove.has(v as T)) as T[]);
|
|
252
|
+
}
|
|
253
|
+
const updated = this.value();
|
|
254
|
+
this.onChange(updated);
|
|
255
|
+
this.valueChange.emit(updated as T | T[]);
|
|
256
|
+
this.cdr.markForCheck();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
onSelectAllCheckedChange(checked: boolean): void {
|
|
260
|
+
this.onToggleSelectAll(checked);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
onOptionCheckedChange(value: T, checked: boolean): void {
|
|
264
|
+
if (!this.isMultiple()) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const currentValue = this.value();
|
|
268
|
+
const current = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
269
|
+
const set = new Set(current);
|
|
270
|
+
if (checked) {
|
|
271
|
+
set.add(value);
|
|
272
|
+
} else {
|
|
273
|
+
set.delete(value);
|
|
274
|
+
}
|
|
275
|
+
this.value.set(Array.from(set) as T[]);
|
|
276
|
+
const updated = this.value();
|
|
277
|
+
this.onChange(updated);
|
|
278
|
+
this.valueChange.emit(updated as T | T[]);
|
|
279
|
+
this.cdr.markForCheck();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
writeValue(value: T | T[] | null): void {
|
|
283
|
+
this.value.set(value);
|
|
284
|
+
this.cdr.markForCheck();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
registerOnChange(fn: (v: T | T[] | null) => void): void {
|
|
288
|
+
this.onChange = fn;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
registerOnTouched(fn: () => void): void {
|
|
292
|
+
this.onTouched = fn;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
setDisabledState(isDisabled: boolean): void {
|
|
296
|
+
this.disabledByControl.set(isDisabled);
|
|
297
|
+
this.cdr.markForCheck();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
get displayValue(): T | T[] | null {
|
|
301
|
+
return this.value();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
toggledAllSelection(){
|
|
305
|
+
if (this.allSelected()) {
|
|
306
|
+
this.onToggleSelectAll(false);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.onToggleSelectAll(true);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
isSelected(value: T): boolean {
|
|
314
|
+
const current = this.value();
|
|
315
|
+
if (!Array.isArray(current)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return current.includes(value);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
trackByVirtualRow(_index: number, row: VirtualRow<T>): string {
|
|
322
|
+
return row.kind === 'selectAll' ? '__select-all__' : String(row.option.value);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.lib-text-field__field {
|
|
6
|
+
width: 100%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.lib-text-field__prefix-image {
|
|
10
|
+
width: 24px;
|
|
11
|
+
height: 24px;
|
|
12
|
+
font-size: 24px;
|
|
13
|
+
overflow: visible;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.lib-text-field__prefix-image svg {
|
|
17
|
+
width: 24px;
|
|
18
|
+
height: 24px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:host[data-state='error'] ::ng-deep .mat-mdc-form-field,
|
|
22
|
+
:host[data-state='error-hover'] ::ng-deep .mat-mdc-form-field,
|
|
23
|
+
:host[data-state='error-focus'] ::ng-deep .mat-mdc-form-field {
|
|
24
|
+
--mdc-outlined-text-field-outline-color: var(--text-fields-outlined-error-outline-outlined-text-field-error-outline-color, #ba1a1a);
|
|
25
|
+
--mdc-outlined-text-field-hover-outline-color: var(--text-fields-outlined-error-hover-outlined-text-field-error-hover-outline-color, #ba1a1a);
|
|
26
|
+
--mdc-outlined-text-field-focus-outline-color: var(--text-fields-outlined-error-focus-outlined-text-field-error-focus-outline-color, #ba1a1a);
|
|
27
|
+
--mdc-outlined-text-field-label-text-color: var(--text-fields-outlined-error-outline-outlined-text-field-error-outline-color, #ba1a1a);
|
|
28
|
+
--mdc-filled-text-field-active-indicator-color: var(--text-fields-outlined-error-outline-outlined-text-field-error-outline-color, #ba1a1a);
|
|
29
|
+
--mdc-filled-text-field-focus-active-indicator-color: var(--text-fields-outlined-error-focus-outlined-text-field-error-focus-outline-color, #ba1a1a);
|
|
30
|
+
--mdc-filled-text-field-label-text-color: var(--text-fields-outlined-error-outline-outlined-text-field-error-outline-color, #ba1a1a);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.lib-text-field__field textarea.mat-mdc-input-element {
|
|
34
|
+
resize: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Hide browser-provided password reveal/clear controls to avoid duplicate icons. */
|
|
38
|
+
:host ::ng-deep input[type='password']::-ms-reveal,
|
|
39
|
+
:host ::ng-deep input[type='password']::-ms-clear {
|
|
40
|
+
display: none;
|
|
41
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<mat-form-field [appearance]="resolvedAppearance()" [style.width]="widthStyle() ?? null" [hideRequiredMarker]="hideRequiredMarker()">
|
|
2
|
+
@if(prefixIconSrc()){
|
|
3
|
+
<mat-icon
|
|
4
|
+
matPrefix
|
|
5
|
+
class="lib-text-field__prefix-image"
|
|
6
|
+
[svgIcon]="prefixIconSrc()"
|
|
7
|
+
(click)="pressSuffixEvent()"
|
|
8
|
+
>
|
|
9
|
+
{{ prefixIconSrc() }}
|
|
10
|
+
</mat-icon>
|
|
11
|
+
} @else if(resolvedPrefixIcon()){
|
|
12
|
+
<mat-icon matPrefix>
|
|
13
|
+
{{ resolvedPrefixIcon() }}
|
|
14
|
+
</mat-icon>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
<mat-label>{{ label() }}</mat-label>
|
|
18
|
+
<input matInput [type]="type()" [formControl]="control()" [placeholder]="placeholder()" [attr.autocomplete]="autocomplete()" />
|
|
19
|
+
|
|
20
|
+
@if(shouldShowError() && error()) {
|
|
21
|
+
<mat-error>{{ error() }}</mat-error>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@if(hint()) {
|
|
25
|
+
<mat-hint>{{ hint() }}</mat-hint>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@if(shouldShowSuffix()) {
|
|
29
|
+
<mat-icon
|
|
30
|
+
matSuffix
|
|
31
|
+
class="lib-text-field__icon lib-text-field__icon--suffix"
|
|
32
|
+
[svgIcon]="suffixIcon()"
|
|
33
|
+
(click)="pressSuffixEvent()"
|
|
34
|
+
>
|
|
35
|
+
{{ suffixTextIcon() }}
|
|
36
|
+
</mat-icon>
|
|
37
|
+
}
|
|
38
|
+
</mat-form-field>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, effect, input, output } from '@angular/core';
|
|
2
|
+
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
3
|
+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
4
|
+
import { MatFormFieldAppearance, MatFormFieldModule } from '@angular/material/form-field';
|
|
5
|
+
import { MatInputModule } from '@angular/material/input';
|
|
6
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
7
|
+
|
|
8
|
+
type LibTextFieldAppearance = MatFormFieldAppearance | 'outlined' | 'filled';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'lib-text-field',
|
|
12
|
+
standalone: true,
|
|
13
|
+
imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatIconModule],
|
|
14
|
+
templateUrl: './text-field.html',
|
|
15
|
+
styleUrl: './text-field.css',
|
|
16
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
17
|
+
})
|
|
18
|
+
export class LibTextFieldComponent {
|
|
19
|
+
private disabledAppliedByInput = false;
|
|
20
|
+
|
|
21
|
+
readonly label = input.required<string>();
|
|
22
|
+
readonly control = input.required<FormControl>();
|
|
23
|
+
readonly appearance = input<LibTextFieldAppearance>('outline');
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Use `appearance`. Kept for compatibility.
|
|
26
|
+
*/
|
|
27
|
+
readonly apperance = input<LibTextFieldAppearance | null>(null);
|
|
28
|
+
readonly suffixIcon = input<string>('');
|
|
29
|
+
readonly suffixTextIcon = input<string>('');
|
|
30
|
+
readonly prefixIcon = input<string>('');
|
|
31
|
+
readonly prefixIconSrc = input<string>('');
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated Use `prefixIcon`. Kept for compatibility.
|
|
34
|
+
*/
|
|
35
|
+
readonly preffixIcon = input<string | null>(null);
|
|
36
|
+
readonly error = input<string | null>(null);
|
|
37
|
+
readonly errorState = input<boolean | null>(null);
|
|
38
|
+
readonly hint = input<string>('');
|
|
39
|
+
readonly suffixEvent = output<void>();
|
|
40
|
+
readonly showSuffix = input(false, { transform: coerceBooleanProperty });
|
|
41
|
+
readonly suffixIconBand = input(false, { transform: coerceBooleanProperty });
|
|
42
|
+
readonly placeholder = input<string>('');
|
|
43
|
+
readonly autocomplete = input<string>('off');
|
|
44
|
+
readonly fullWidth = input(false, { transform: coerceBooleanProperty });
|
|
45
|
+
readonly width = input<string | null, string | number | null>(null, {
|
|
46
|
+
transform: (value) => {
|
|
47
|
+
if (value == null || value === '') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return typeof value === 'number' ? `${value}px` : String(value);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
readonly disabled = input(false, { transform: coerceBooleanProperty });
|
|
54
|
+
readonly type = input<string>('text');
|
|
55
|
+
readonly hideRequiredMarker = input(false, { transform: coerceBooleanProperty });
|
|
56
|
+
|
|
57
|
+
readonly resolvedAppearance = computed<MatFormFieldAppearance>(() => {
|
|
58
|
+
const raw = this.apperance() ?? this.appearance();
|
|
59
|
+
if (raw === 'outlined') return 'outline';
|
|
60
|
+
if (raw === 'filled') return 'fill';
|
|
61
|
+
return raw;
|
|
62
|
+
});
|
|
63
|
+
readonly resolvedPrefixIcon = computed(() => this.preffixIcon() ?? this.prefixIcon());
|
|
64
|
+
readonly widthStyle = computed(() => (this.fullWidth() ? '100%' : this.width()));
|
|
65
|
+
readonly shouldShowSuffix = computed(
|
|
66
|
+
() => this.showSuffix() || (this.suffixIconBand() && (!!this.suffixIcon() || !!this.suffixTextIcon()))
|
|
67
|
+
);
|
|
68
|
+
readonly shouldShowError = computed(() => {
|
|
69
|
+
const forcedError = this.errorState();
|
|
70
|
+
if (forcedError !== null) {
|
|
71
|
+
return forcedError;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const control = this.control();
|
|
75
|
+
return control.invalid && (control.dirty || control.touched);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
constructor() {
|
|
79
|
+
effect(() => {
|
|
80
|
+
const control = this.control();
|
|
81
|
+
const shouldDisable = this.disabled();
|
|
82
|
+
|
|
83
|
+
if (shouldDisable) {
|
|
84
|
+
if (control.enabled) {
|
|
85
|
+
control.disable({ emitEvent: false });
|
|
86
|
+
}
|
|
87
|
+
this.disabledAppliedByInput = true;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.disabledAppliedByInput && control.disabled) {
|
|
92
|
+
control.enable({ emitEvent: false });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.disabledAppliedByInput = false;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pressSuffixEvent(): void {
|
|
100
|
+
this.suffixEvent.emit();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
width: 100%;
|
|
4
|
+
--bars-common-app-bar-brand-width: 47;
|
|
5
|
+
--bars-common-app-bar-brand-height: 25;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.app-bar {
|
|
9
|
+
display: grid;
|
|
10
|
+
grid-template-columns: auto 1fr auto;
|
|
11
|
+
align-items: center;
|
|
12
|
+
width: 100%;
|
|
13
|
+
flex-shrink: 0;
|
|
14
|
+
align-self: stretch;
|
|
15
|
+
height: calc(var(--bars-size-small-app-bar-small-container-height, 64) * 1px);
|
|
16
|
+
padding: 0.5rem calc(var(--bars-common-app-bar-right-padding, 4) * 1px) 0.5rem calc(var(--bars-common-app-bar-left-padding, 4) * 1px);
|
|
17
|
+
background-color: var(--bars-common-app-bar-container-color, #ebeef0);
|
|
18
|
+
border-radius: calc(var(--bars-common-app-bar-container-shape, 0) * 1px);
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
column-gap: calc(var(--bars-common-app-bar-icon-spacing, 0) * 1px);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.app-bar__leading,
|
|
24
|
+
.app-bar__actions {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.app-bar__leading {
|
|
30
|
+
justify-self: start;
|
|
31
|
+
width: calc(var(--bars-common-app-bar-icon-touch-target-size, 48) * 1px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.app-bar__actions {
|
|
35
|
+
justify-self: end;
|
|
36
|
+
justify-content: flex-end;
|
|
37
|
+
gap: calc(var(--bars-common-app-bar-icon-spacing, 0) * 1px);
|
|
38
|
+
min-width: calc(var(--bars-common-app-bar-icon-touch-target-size, 48) * 1px);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.app-bar__actions:empty {
|
|
42
|
+
min-width: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.app-bar__brand {
|
|
46
|
+
justify-self: center;
|
|
47
|
+
position: relative;
|
|
48
|
+
width: calc(var(--bars-common-app-bar-brand-width, 47) * 1px);
|
|
49
|
+
height: calc(var(--bars-common-app-bar-brand-height, 25) * 1px);
|
|
50
|
+
aspect-ratio: 47 / 25;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
color: var(--bars-common-app-bar-central-icon, #e42313);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.app-bar__brand-icon {
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
aspect-ratio: 47 / 25;
|
|
62
|
+
display: block;
|
|
63
|
+
object-fit: contain;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.app-bar__brand-icon use {
|
|
67
|
+
width: 100% !important;
|
|
68
|
+
height: 100% !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.app-bar__brand-dot {
|
|
72
|
+
position: absolute;
|
|
73
|
+
width: 6px;
|
|
74
|
+
height: 6px;
|
|
75
|
+
border-radius: 50%;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.app-bar__brand-dot--primary {
|
|
79
|
+
top: 6px;
|
|
80
|
+
left: 6px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.app-bar__brand-dot--secondary {
|
|
84
|
+
top: 3px;
|
|
85
|
+
left: 16px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.app-bar__brand-dot--tertiary {
|
|
89
|
+
top: 8px;
|
|
90
|
+
left: 26px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.app-bar__brand-curve {
|
|
94
|
+
position: absolute;
|
|
95
|
+
bottom: 0;
|
|
96
|
+
right: 0;
|
|
97
|
+
width: 32px;
|
|
98
|
+
height: 18px;
|
|
99
|
+
border-bottom: 4px solid var(--bars-common-app-bar-central-icon, #e42313);
|
|
100
|
+
border-right: 4px solid var(--bars-common-app-bar-central-icon, #e42313);
|
|
101
|
+
border-left: 0;
|
|
102
|
+
border-top: 0;
|
|
103
|
+
border-radius: 0 0 24px 24px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.app-bar__icon-button {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
width: 48px;
|
|
111
|
+
height: 48px;
|
|
112
|
+
flex: 0 0 auto;
|
|
113
|
+
border: 0;
|
|
114
|
+
border-radius: calc(var(--icon-buttons-size-small-icon-button-small-container-shape-round, 1000) * 1px);
|
|
115
|
+
background: transparent;
|
|
116
|
+
color: var(--bars-common-app-bar-leading-icon, #181c1e);
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
transition: background-color 150ms ease, color 150ms ease;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.app-bar__icon-button--trailing {
|
|
122
|
+
color: var(--bars-common-app-bar-trailing-icon, #434749);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.app-bar__icon-button:hover,
|
|
126
|
+
.app-bar__icon-button:focus-visible {
|
|
127
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.app-bar__icon-button:focus-visible {
|
|
131
|
+
outline: 2px solid rgba(0, 0, 0, 0.24);
|
|
132
|
+
outline-offset: 2px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.app-bar__icon {
|
|
136
|
+
font-size: calc(var(--bars-common-app-bar-icon-size, 24) * 1px);
|
|
137
|
+
width: 1em;
|
|
138
|
+
height: 1em;
|
|
139
|
+
line-height: 1;
|
|
140
|
+
display: block;
|
|
141
|
+
object-fit: contain;
|
|
142
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<header class="app-bar" role="banner">
|
|
2
|
+
<div class="app-bar__leading">
|
|
3
|
+
@if(showLeading()) {
|
|
4
|
+
<button type="button" class="app-bar__icon-button app-bar__icon-button--leading" (click)="onLeadingClick()"
|
|
5
|
+
[attr.aria-label]="leadingActionLabel()">
|
|
6
|
+
@if (leadingIconUrl(); as leadingIconSrc) {
|
|
7
|
+
<img class="app-bar__icon" [src]="leadingIconSrc" alt="" aria-hidden="true" />
|
|
8
|
+
}
|
|
9
|
+
</button>
|
|
10
|
+
}
|
|
11
|
+
</div>
|
|
12
|
+
<div class="app-bar__brand" role="img" [attr.aria-label]="brandLabel()">
|
|
13
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="47" height="25" viewBox="0 0 47 25" fill="none">
|
|
14
|
+
<path
|
|
15
|
+
d="M34.0017 2.67521e-05C30.6579 -0.00672338 27.4427 1.26417 25.0377 3.54336C22.6327 5.82255 21.2268 8.93092 21.1179 12.2097C21.1179 12.3304 21.0564 12.5274 21.0564 12.6959C21.0564 14.8068 20.2017 16.8311 18.6803 18.3237C17.1589 19.8163 15.0955 20.6548 12.9439 20.6548C10.7923 20.6548 8.72889 19.8163 7.2075 18.3237C5.68612 16.8311 4.83141 14.8068 4.83141 12.6959C4.83073 12.0678 4.57609 11.4656 4.12335 11.0214C3.67061 10.5773 3.05676 10.3274 2.4165 10.3268C2.09598 10.3218 1.77774 10.3805 1.48091 10.4992C1.18407 10.6179 0.914746 10.7943 0.689116 11.0177C0.463486 11.2411 0.286201 11.5069 0.167908 11.7992C0.0496149 12.0915 -0.00724651 12.4042 0.000738184 12.7186C0.114789 15.972 1.49612 19.0588 3.86027 21.3431C6.22441 23.6274 9.39139 24.9353 12.709 24.9977C16.0266 25.06 19.2423 23.8719 21.6939 21.678C24.1454 19.4842 25.6462 16.4516 25.8871 13.2048C25.9417 13.0487 25.9626 12.8831 25.9486 12.7186C25.9486 10.6078 26.8033 8.58341 28.3247 7.09083C29.8461 5.59826 31.9095 4.75974 34.0611 4.75974C36.2127 4.75974 38.2761 5.59826 39.7975 7.09083C41.3189 8.58341 42.1736 10.6078 42.1736 12.7186C42.1562 13.0399 42.2056 13.3613 42.3189 13.6632C42.4322 13.9651 42.607 14.2412 42.8327 14.4747C43.0583 14.7082 43.3301 14.8941 43.6313 15.0212C43.9326 15.1482 44.2571 15.2138 44.5851 15.2138C44.913 15.2138 45.2375 15.1482 45.5388 15.0212C45.8401 14.8941 46.1119 14.7082 46.3375 14.4747C46.5632 14.2412 46.738 13.9651 46.8513 13.6632C46.9646 13.3613 47.014 13.0399 46.9966 12.7186C46.99 9.34216 45.6178 6.10632 43.1814 3.72165C40.7449 1.33699 37.4433 -0.00153433 34.0017 2.67521e-05Z"
|
|
16
|
+
fill="#E42313" />
|
|
17
|
+
<path
|
|
18
|
+
d="M10.5943 6.75042C10.5937 7.32697 10.4188 7.89037 10.0918 8.36943C9.76482 8.84848 9.30041 9.22168 8.75728 9.44185C8.21414 9.66203 7.61667 9.71929 7.04037 9.60641C6.46407 9.49352 5.93482 9.21556 5.51951 8.80765C5.1042 8.39974 4.82148 7.88019 4.70708 7.31468C4.59268 6.74917 4.65174 6.16307 4.87678 5.63048C5.10183 5.09789 5.48277 4.64271 5.97145 4.32247C6.46013 4.00222 7.03461 3.8313 7.62228 3.8313C8.40773 3.84195 9.15792 4.15302 9.71305 4.69826C10.2682 5.24351 10.5844 5.97984 10.5943 6.75042Z"
|
|
19
|
+
fill="#E42313" />
|
|
20
|
+
<path
|
|
21
|
+
d="M19.3841 6.75044C19.3841 7.52375 19.0709 8.26539 18.5136 8.8122C17.9562 9.35901 17.2003 9.66621 16.412 9.66621C15.6238 9.66621 14.8678 9.35901 14.3104 8.8122C13.7531 8.26539 13.44 7.52375 13.44 6.75044C13.4347 6.36594 13.5079 5.9843 13.6554 5.62802C13.8028 5.27174 14.0215 4.94802 14.2985 4.67596C14.5755 4.4039 14.9052 4.18901 15.2682 4.04394C15.6312 3.89888 16.0201 3.82658 16.412 3.83132C17.1975 3.84196 17.9476 4.15303 18.5028 4.69828C19.0579 5.24352 19.3741 5.97985 19.3841 6.75044Z"
|
|
22
|
+
fill="#E42313" />
|
|
23
|
+
</svg>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="app-bar__actions">
|
|
27
|
+
@for (action of trailingActions(); track trackByAction($index, action)) {
|
|
28
|
+
<button type="button" class="app-bar__icon-button app-bar__icon-button--trailing" (click)="onActionClick(action)"
|
|
29
|
+
[attr.aria-label]="action.ariaLabel">
|
|
30
|
+
@if (actionIconUrl(action); as actionIconSrc) {
|
|
31
|
+
<img class="app-bar__icon" [src]="actionIconSrc" alt="" aria-hidden="true" />
|
|
32
|
+
}
|
|
33
|
+
</button>
|
|
34
|
+
}
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|