@up_si_vale/sivale-componentes-angular 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.
Files changed (40) hide show
  1. package/README.md +81 -0
  2. package/ng-package.json +9 -0
  3. package/package.json +40 -0
  4. package/src/lib/atoms/button/button.component.html +21 -0
  5. package/src/lib/atoms/button/button.component.scss +242 -0
  6. package/src/lib/atoms/button/button.component.ts +43 -0
  7. package/src/lib/atoms/checkbox/checkbox.component.scss +131 -0
  8. package/src/lib/atoms/checkbox/checkbox.component.ts +130 -0
  9. package/src/lib/atoms/date-picker/date-picker.component.html +295 -0
  10. package/src/lib/atoms/date-picker/date-picker.component.scss +1002 -0
  11. package/src/lib/atoms/date-picker/date-picker.component.ts +942 -0
  12. package/src/lib/atoms/input/input.component.ts +239 -0
  13. package/src/lib/atoms/loader/loader.component.scss +144 -0
  14. package/src/lib/atoms/loader/loader.component.ts +44 -0
  15. package/src/lib/atoms/radio/radio.component.scss +115 -0
  16. package/src/lib/atoms/radio/radio.component.ts +103 -0
  17. package/src/lib/atoms/textarea/textarea.component.ts +155 -0
  18. package/src/lib/directives/asset-source.directive.ts +64 -0
  19. package/src/lib/directives/icon-source.directive.ts +74 -0
  20. package/src/lib/directives/no-leading-space.directive.ts +18 -0
  21. package/src/lib/directives/only-letters.directive.ts +21 -0
  22. package/src/lib/directives/only-number.directive.ts +15 -0
  23. package/src/lib/directives/rfc.directive.ts +60 -0
  24. package/src/lib/directives/tooltip.directive.ts +211 -0
  25. package/src/lib/models/snackbar.models.ts +16 -0
  26. package/src/lib/molecules/copy-input/copy-input.component.html +29 -0
  27. package/src/lib/molecules/copy-input/copy-input.component.scss +101 -0
  28. package/src/lib/molecules/copy-input/copy-input.component.ts +41 -0
  29. package/src/lib/molecules/multiselect/multiselect.component.scss +298 -0
  30. package/src/lib/molecules/multiselect/multiselect.component.ts +487 -0
  31. package/src/lib/molecules/option/option.component.ts +45 -0
  32. package/src/lib/molecules/phone-input/phone-input.component.scss +78 -0
  33. package/src/lib/molecules/phone-input/phone-input.component.ts +43 -0
  34. package/src/lib/molecules/select/select.component.ts +482 -0
  35. package/src/lib/molecules/snackbar/snackbar.component.scss +201 -0
  36. package/src/lib/molecules/snackbar/snackbar.component.ts +321 -0
  37. package/src/lib/services/snackbar.service.ts +103 -0
  38. package/src/lib/tokens/asset-base-url.token.ts +10 -0
  39. package/src/public-api.ts +38 -0
  40. package/tsconfig.json +31 -0
@@ -0,0 +1,155 @@
1
+ import { Component, Input, forwardRef, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms';
4
+
5
+ @Component({
6
+ selector: 'app-textarea',
7
+ standalone: true,
8
+ imports: [CommonModule, ReactiveFormsModule, FormsModule],
9
+ changeDetection: ChangeDetectionStrategy.OnPush,
10
+ template: `
11
+ <div class="textarea-wrapper">
12
+ <label *ngIf="label" [for]="id" class="textarea-label">{{ label }}</label>
13
+ <div class="textarea-container" [class.error]="hasError">
14
+ <textarea
15
+ [id]="id"
16
+ [placeholder]="placeholder"
17
+ [rows]="rows"
18
+ [disabled]="disabled"
19
+ [readonly]="readonly"
20
+ [attr.maxlength]="maxLength"
21
+ class="textarea-field"
22
+ [class.error]="hasError"
23
+ [value]="value"
24
+ (blur)="onBlur()"
25
+ (input)="onInputChange($event)"
26
+ ></textarea>
27
+ </div>
28
+ <div *ngIf="hasError && errorMessage" class="field-error">
29
+ {{ errorMessage }}
30
+ </div>
31
+ </div>
32
+ `,
33
+ styles: [`
34
+ .textarea-wrapper {
35
+ width: 100%;
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 6px;
39
+ }
40
+
41
+ .textarea-label {
42
+ font-family: 'Quicksand', sans-serif;
43
+ font-weight: 500;
44
+ font-size: 14px;
45
+ line-height: 20px;
46
+ color: #414651;
47
+ }
48
+
49
+ .textarea-container {
50
+ width: 100%;
51
+
52
+ &.error .textarea-field {
53
+ border-color: #F04438;
54
+ }
55
+ }
56
+
57
+ .textarea-field {
58
+ width: 100%;
59
+ padding: 10px 14px;
60
+ background: #FFFFFF;
61
+ border: 1px solid #D5D7DA;
62
+ border-radius: 8px;
63
+ font-family: 'Quicksand', sans-serif;
64
+ font-weight: 400;
65
+ font-size: 16px;
66
+ line-height: 24px;
67
+ color: #2E353A;
68
+ resize: vertical;
69
+ min-height: 100px;
70
+ transition: all 0.2s ease;
71
+ box-sizing: border-box;
72
+
73
+ &::placeholder {
74
+ color: #A4A7AE;
75
+ }
76
+
77
+ &:focus {
78
+ outline: none;
79
+ border-color: #F59100;
80
+ box-shadow: 0 0 0 4px rgba(245, 145, 0, 0.1);
81
+ }
82
+
83
+ &:disabled {
84
+ background: #F9FAFB;
85
+ cursor: not-allowed;
86
+ }
87
+
88
+ &.error {
89
+ border-color: #F04438;
90
+
91
+ &:focus {
92
+ box-shadow: 0 0 0 4px rgba(240, 68, 56, 0.1);
93
+ }
94
+ }
95
+ }
96
+
97
+ .field-error {
98
+ color: #F04438;
99
+ font-size: 0.875rem;
100
+ font-family: 'Quicksand', sans-serif;
101
+ }
102
+ `],
103
+ providers: [
104
+ {
105
+ provide: NG_VALUE_ACCESSOR,
106
+ useExisting: forwardRef(() => TextareaComponent),
107
+ multi: true
108
+ }
109
+ ]
110
+ })
111
+ export class TextareaComponent implements ControlValueAccessor {
112
+ @Input() label = '';
113
+ @Input() id = '';
114
+ @Input() placeholder = '';
115
+ @Input() rows = 4;
116
+ @Input() maxLength?: number;
117
+ @Input() hasError = false;
118
+ @Input() errorMessage = '';
119
+ @Input() disabled = false;
120
+ @Input() readonly = false;
121
+
122
+ value = '';
123
+ onChange: any = () => {};
124
+ onTouched: any = () => {};
125
+
126
+ constructor(private cdr: ChangeDetectorRef) {}
127
+
128
+ writeValue(value: any): void {
129
+ this.value = value || '';
130
+ this.cdr.markForCheck();
131
+ }
132
+
133
+ registerOnChange(fn: any): void {
134
+ this.onChange = fn;
135
+ }
136
+
137
+ registerOnTouched(fn: any): void {
138
+ this.onTouched = fn;
139
+ }
140
+
141
+ setDisabledState(isDisabled: boolean): void {
142
+ this.disabled = isDisabled;
143
+ this.cdr.markForCheck();
144
+ }
145
+
146
+ onInputChange(event: Event): void {
147
+ const target = event.target as HTMLTextAreaElement;
148
+ this.value = target.value;
149
+ this.onChange(this.value);
150
+ }
151
+
152
+ onBlur(): void {
153
+ this.onTouched();
154
+ }
155
+ }
@@ -0,0 +1,64 @@
1
+ import { Directive, Input, ElementRef, OnInit, OnChanges, AfterViewInit, Inject, Optional } from '@angular/core';
2
+ import { ASSET_BASE_URL } from '../tokens/asset-base-url.token';
3
+
4
+ export type AssetSource = 'mf' | 'main';
5
+
6
+ @Directive({
7
+ selector: '[assetSource]',
8
+ standalone: true
9
+ })
10
+ export class AssetSourceDirective implements OnInit, OnChanges, AfterViewInit {
11
+ @Input() assetSource: AssetSource = 'mf';
12
+ @Input() assetPath?: string;
13
+
14
+ private originalSrc: string | null = null;
15
+
16
+ constructor(
17
+ private el: ElementRef<HTMLImageElement>,
18
+ @Optional() @Inject(ASSET_BASE_URL) private assetBaseUrl: string | null
19
+ ) {}
20
+
21
+ ngOnInit() {
22
+ this.captureOriginalSrc();
23
+ }
24
+
25
+ ngAfterViewInit() {
26
+ this.updateSrc();
27
+ }
28
+
29
+ ngOnChanges() {
30
+ this.updateSrc();
31
+ }
32
+
33
+ private captureOriginalSrc() {
34
+ if (this.el.nativeElement.tagName === 'IMG') {
35
+ this.originalSrc = this.el.nativeElement.getAttribute('src');
36
+ }
37
+ }
38
+
39
+ private updateSrc() {
40
+ const assetPath = this.assetPath || this.originalSrc;
41
+
42
+ if (!assetPath) {
43
+ return;
44
+ }
45
+
46
+ const baseUrl = this.getBaseUrl();
47
+ const fullPath = `${baseUrl}/${assetPath}`;
48
+
49
+ if (this.el.nativeElement.tagName === 'IMG') {
50
+ this.el.nativeElement.src = fullPath;
51
+ }
52
+ }
53
+
54
+ private getBaseUrl(): string {
55
+ switch (this.assetSource) {
56
+ case 'mf':
57
+ return this.assetBaseUrl ?? '';
58
+ case 'main':
59
+ return '/assets';
60
+ default:
61
+ return this.assetBaseUrl ?? '';
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,74 @@
1
+ import { Directive, Input, ElementRef, OnInit, OnChanges, AfterViewInit, Inject, Optional } from '@angular/core';
2
+ import { ASSET_BASE_URL } from '../tokens/asset-base-url.token';
3
+
4
+ export type IconName =
5
+ 'user'
6
+ | 'mail'
7
+ | 'build'
8
+ | 'map-pin'
9
+ | 'money'
10
+ | 'card'
11
+ | 'nav-arrow-up'
12
+ | 'nav-arrow-down'
13
+ | 'car'
14
+ | 'trash'
15
+ | 'hand-card'
16
+ | 'help-outlined'
17
+ | 'edit'
18
+ | 'shopping-cart';
19
+
20
+ @Directive({
21
+ selector: 'i[iconName]',
22
+ standalone: true
23
+ })
24
+ export class IconSourceDirective implements OnInit, OnChanges, AfterViewInit {
25
+ @Input() iconName!: IconName;
26
+ @Input() assetSource: 'mf' | 'main' = 'mf';
27
+
28
+ constructor(
29
+ private el: ElementRef<HTMLElement>,
30
+ @Optional() @Inject(ASSET_BASE_URL) private assetBaseUrl: string | null
31
+ ) {
32
+ }
33
+
34
+ ngOnInit() {
35
+ this.addIconClass();
36
+ }
37
+
38
+ ngAfterViewInit() {
39
+ this.setIconUrl();
40
+ }
41
+
42
+ ngOnChanges() {
43
+ this.setIconUrl();
44
+ }
45
+
46
+ private addIconClass() {
47
+ if (this.iconName) {
48
+ this.el.nativeElement.classList.add(`icon-${this.iconName}`);
49
+ }
50
+ }
51
+
52
+ private setIconUrl() {
53
+ if (!this.iconName) {
54
+ return;
55
+ }
56
+
57
+ const baseUrl = this.getBaseUrl();
58
+ const iconUrl = `${baseUrl}/icons/${this.iconName}.svg`;
59
+
60
+ this.el.nativeElement.style.maskImage = `url('${iconUrl}')`;
61
+ this.el.nativeElement.style.webkitMaskImage = `url('${iconUrl}')`;
62
+ }
63
+
64
+ private getBaseUrl(): string {
65
+ switch (this.assetSource) {
66
+ case 'mf':
67
+ return this.assetBaseUrl ?? '';
68
+ case 'main':
69
+ return '/assets';
70
+ default:
71
+ return this.assetBaseUrl ?? '';
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,18 @@
1
+ import { Directive, HostListener } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[noLeadingSpace]',
5
+ standalone: true
6
+ })
7
+ export class NoLeadingSpaceDirective {
8
+
9
+ @HostListener('input', ['$event'])
10
+ onInput(event: any) {
11
+ const value = event.target.value;
12
+
13
+ // Elimina espacios al inicio
14
+ if (value.startsWith(' ')) {
15
+ event.target.value = value.trimStart();
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,21 @@
1
+ import { Directive, HostListener } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[onlyLetters]',
5
+ standalone: true
6
+ })
7
+
8
+ export class OnlyLettersDirective {
9
+
10
+ private regex = /^[A-Za-zÁÉÍÓÚÜÑáéíóúüñ' -]*$/;
11
+
12
+ @HostListener('input', ['$event'])
13
+ onInput(event: any) {
14
+ const value: string = event.target.value;
15
+
16
+ event.target.value = value
17
+ .split('')
18
+ .filter(char => this.regex.test(char))
19
+ .join('');
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ import { Directive, HostListener } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[onlyNumber]',
5
+ standalone: true
6
+ })
7
+
8
+ export class OnlyNumberDirective {
9
+ @HostListener('input', ['$event'])
10
+ onInput(event: any) {
11
+ const value = event.target.value;
12
+ event.target.value = value.replace(/\D/g, '');
13
+ }
14
+
15
+ }
@@ -0,0 +1,60 @@
1
+ import { Directive, HostListener, Input, Optional, Self } from '@angular/core';
2
+ import { NgControl } from '@angular/forms';
3
+
4
+ @Directive({
5
+ selector: '[rfcValidator]',
6
+ standalone: true
7
+ })
8
+ export class RfcDirective {
9
+
10
+ @Input() tipoContribuyente: string | null = null;
11
+
12
+ constructor(@Optional() @Self() private ngControl: NgControl | null) {
13
+ }
14
+
15
+ // Función para obtener el límite según el tipo de contribuyente
16
+ private getMaxLength(): number {
17
+ if (this.tipoContribuyente === 'fisica') return 13;
18
+ if (this.tipoContribuyente === 'moral') return 12;
19
+ return 13; // Default por si no llega el valor
20
+ }
21
+
22
+ @HostListener('input', ['$event'])
23
+ onInput(event: Event) {
24
+ const input = event.target as HTMLInputElement;
25
+ let value = input.value;
26
+
27
+ // Solo letras y números
28
+ value = value.replace(/[^a-zA-Z0-9]/g, '');
29
+
30
+ // Mayúsculas
31
+ value = value.toUpperCase();
32
+
33
+ // Aplicar límite dinámico
34
+ const max = this.getMaxLength();
35
+ if (value.length > max) {
36
+ value = value.substring(0, max);
37
+ }
38
+
39
+ this.ngControl?.control?.setValue(value, { emitEvent: false });
40
+ }
41
+
42
+ @HostListener('paste', ['$event'])
43
+ onPaste(event: ClipboardEvent) {
44
+ const pasted = event.clipboardData?.getData('text') || '';
45
+ const max = this.getMaxLength();
46
+
47
+ // Bloquear pegado con caracteres inválidos
48
+ if (/[^a-zA-Z0-9]/.test(pasted)) {
49
+ event.preventDefault();
50
+ return;
51
+ }
52
+
53
+ // Bloquear pegado si excede el máximo
54
+ if (pasted.length > max) {
55
+ event.preventDefault();
56
+ }
57
+ }
58
+ }
59
+
60
+
@@ -0,0 +1,211 @@
1
+ import {
2
+ Directive,
3
+ Input,
4
+ ElementRef,
5
+ HostListener,
6
+ Renderer2,
7
+ OnDestroy,
8
+ HostBinding
9
+ } from '@angular/core';
10
+
11
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';
12
+
13
+ @Directive({
14
+ selector: '[appTooltip]',
15
+ standalone: true
16
+ })
17
+ export class TooltipDirective implements OnDestroy {
18
+ @Input('appTooltip') tooltipText = '';
19
+ @Input() tooltipPosition: TooltipPosition = 'auto';
20
+ @Input() tooltipDelay = 300;
21
+ @Input() tooltipMaxWidth = 280;
22
+ @Input() tooltipClass = '';
23
+
24
+ @HostBinding('style.cursor') cursor = 'help';
25
+
26
+ private tooltipElement: HTMLElement | null = null;
27
+ private showTimeout?: number;
28
+ private hideTimeout?: number;
29
+
30
+ constructor(
31
+ private el: ElementRef,
32
+ private renderer: Renderer2
33
+ ) {}
34
+
35
+ @HostListener('mouseenter')
36
+ onMouseEnter(): void {
37
+ if (this.hideTimeout) {
38
+ clearTimeout(this.hideTimeout);
39
+ this.hideTimeout = undefined;
40
+ }
41
+
42
+ this.showTimeout = window.setTimeout(() => {
43
+ this.showTooltip();
44
+ }, this.tooltipDelay);
45
+ }
46
+
47
+ @HostListener('mouseleave')
48
+ onMouseLeave(): void {
49
+ if (this.showTimeout) {
50
+ clearTimeout(this.showTimeout);
51
+ this.showTimeout = undefined;
52
+ }
53
+
54
+ this.hideTimeout = window.setTimeout(() => {
55
+ this.hideTooltip();
56
+ }, 100);
57
+ }
58
+
59
+ @HostListener('click')
60
+ onClick(): void {
61
+ // Allow click events to propagate
62
+ }
63
+
64
+ private showTooltip(): void {
65
+ if (!this.tooltipText || this.tooltipElement) {
66
+ return;
67
+ }
68
+
69
+ // Create tooltip element
70
+ this.tooltipElement = this.renderer.createElement('div');
71
+ this.renderer.addClass(this.tooltipElement, 'app-tooltip');
72
+
73
+ if (this.tooltipClass) {
74
+ this.renderer.addClass(this.tooltipElement, this.tooltipClass);
75
+ }
76
+
77
+ // Create tooltip content
78
+ const tooltipContent = this.renderer.createElement('div');
79
+ this.renderer.addClass(tooltipContent, 'app-tooltip-content');
80
+ this.renderer.setStyle(tooltipContent, 'max-width', `${this.tooltipMaxWidth}px`);
81
+
82
+ const textNode = this.renderer.createText(this.tooltipText);
83
+ this.renderer.appendChild(tooltipContent, textNode);
84
+ this.renderer.appendChild(this.tooltipElement, tooltipContent);
85
+
86
+ // Create arrow element
87
+ const arrow = this.renderer.createElement('div');
88
+ this.renderer.addClass(arrow, 'app-tooltip-arrow');
89
+ this.renderer.appendChild(this.tooltipElement, arrow);
90
+
91
+ // Add to body
92
+ this.renderer.appendChild(document.body, this.tooltipElement);
93
+
94
+ // Position tooltip
95
+ this.positionTooltip();
96
+
97
+ // Trigger animation
98
+ requestAnimationFrame(() => {
99
+ if (this.tooltipElement) {
100
+ this.renderer.addClass(this.tooltipElement, 'app-tooltip-show');
101
+ }
102
+ });
103
+ }
104
+
105
+ private hideTooltip(): void {
106
+ if (!this.tooltipElement) {
107
+ return;
108
+ }
109
+
110
+ this.renderer.removeClass(this.tooltipElement, 'app-tooltip-show');
111
+
112
+ setTimeout(() => {
113
+ if (this.tooltipElement) {
114
+ this.renderer.removeChild(document.body, this.tooltipElement);
115
+ this.tooltipElement = null;
116
+ }
117
+ }, 200);
118
+ }
119
+
120
+ private positionTooltip(): void {
121
+ if (!this.tooltipElement) {
122
+ return;
123
+ }
124
+
125
+ const hostRect = this.el.nativeElement.getBoundingClientRect();
126
+ const tooltipRect = this.tooltipElement.getBoundingClientRect();
127
+ const scrollX = window.scrollX || window.pageXOffset;
128
+ const scrollY = window.scrollY || window.pageYOffset;
129
+ const gap = 16; // Space between element and tooltip
130
+
131
+ // Calculate available space in each direction
132
+ const spaceTop = hostRect.top;
133
+ const spaceBottom = window.innerHeight - hostRect.bottom;
134
+ const spaceLeft = hostRect.left;
135
+ const spaceRight = window.innerWidth - hostRect.right;
136
+
137
+ let position = this.tooltipPosition;
138
+ let top = 0;
139
+ let left = 0;
140
+
141
+ // Auto-detect best position
142
+ if (position === 'auto') {
143
+ if (spaceTop >= tooltipRect.height + gap) {
144
+ position = 'top';
145
+ } else if (spaceBottom >= tooltipRect.height + gap) {
146
+ position = 'bottom';
147
+ } else if (spaceRight >= tooltipRect.width + gap) {
148
+ position = 'right';
149
+ } else if (spaceLeft >= tooltipRect.width + gap) {
150
+ position = 'left';
151
+ } else {
152
+ // Default to top if no space is sufficient
153
+ position = 'top';
154
+ }
155
+ }
156
+
157
+ // Calculate position based on detected/specified position
158
+ switch (position) {
159
+ case 'top':
160
+ top = hostRect.top + scrollY - tooltipRect.height - gap;
161
+ left = hostRect.left + scrollX + (hostRect.width / 2) - (tooltipRect.width / 2);
162
+ break;
163
+
164
+ case 'bottom':
165
+ top = hostRect.bottom + scrollY + gap;
166
+ left = hostRect.left + scrollX + (hostRect.width / 2) - (tooltipRect.width / 2);
167
+ break;
168
+
169
+ case 'left':
170
+ top = hostRect.top + scrollY + (hostRect.height / 2) - (tooltipRect.height / 2);
171
+ left = hostRect.left + scrollX - tooltipRect.width - gap;
172
+ break;
173
+
174
+ case 'right':
175
+ top = hostRect.top + scrollY + (hostRect.height / 2) - (tooltipRect.height / 2);
176
+ left = hostRect.right + scrollX + gap;
177
+ break;
178
+ }
179
+
180
+ // Adjust horizontal position to keep tooltip within viewport
181
+ if (position === 'top' || position === 'bottom') {
182
+ const minLeft = scrollX + 10;
183
+ const maxLeft = scrollX + window.innerWidth - tooltipRect.width - 10;
184
+ left = Math.max(minLeft, Math.min(left, maxLeft));
185
+ }
186
+
187
+ // Adjust vertical position to keep tooltip within viewport
188
+ if (position === 'left' || position === 'right') {
189
+ const minTop = scrollY + 10;
190
+ const maxTop = scrollY + window.innerHeight - tooltipRect.height - 10;
191
+ top = Math.max(minTop, Math.min(top, maxTop));
192
+ }
193
+
194
+ // Apply position
195
+ this.renderer.setStyle(this.tooltipElement, 'top', `${top}px`);
196
+ this.renderer.setStyle(this.tooltipElement, 'left', `${left}px`);
197
+
198
+ // Add position class for arrow styling
199
+ this.renderer.addClass(this.tooltipElement, `app-tooltip-${position}`);
200
+ }
201
+
202
+ ngOnDestroy(): void {
203
+ if (this.showTimeout) {
204
+ clearTimeout(this.showTimeout);
205
+ }
206
+ if (this.hideTimeout) {
207
+ clearTimeout(this.hideTimeout);
208
+ }
209
+ this.hideTooltip();
210
+ }
211
+ }
@@ -0,0 +1,16 @@
1
+ export type SnackbarType = 'success' | 'error' | 'warning' | 'info';
2
+ export type SnackbarPosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
3
+
4
+ export interface SnackbarConfig {
5
+ message: string;
6
+ type: SnackbarType;
7
+ duration?: number;
8
+ position?: SnackbarPosition;
9
+ action?: string;
10
+ actionCallback?: () => void;
11
+ }
12
+
13
+ export interface SnackbarData extends SnackbarConfig {
14
+ id: string;
15
+ position: SnackbarPosition;
16
+ }
@@ -0,0 +1,29 @@
1
+ <div class="copy-input-wrapper">
2
+
3
+ <!-- Tooltip -->
4
+ <div
5
+ class="copy-tooltip"
6
+ *ngIf="isCopied"
7
+ >
8
+ Copiado
9
+ </div>
10
+
11
+ <!-- Componente -->
12
+ <div
13
+ class="copy-input"
14
+ (click)="copyToClipboard()"
15
+ tabindex="0"
16
+ role="button"
17
+ [attr.aria-label]="ariaLabel"
18
+ (keydown.enter)="copyToClipboard()"
19
+ >
20
+ <i
21
+ class="copy-input__icon"
22
+ [ngClass]="isCopied ? copiedIcon : icon">
23
+ </i>
24
+
25
+ <span class="copy-input__text">
26
+ {{ value }}
27
+ </span>
28
+ </div>
29
+ </div>