@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,482 @@
1
+ import {
2
+ Component,
3
+ Input,
4
+ forwardRef,
5
+ ViewChild,
6
+ ElementRef,
7
+ OnDestroy,
8
+ HostListener,
9
+ Renderer2,
10
+ ContentChildren,
11
+ ContentChild,
12
+ QueryList,
13
+ AfterContentInit,
14
+ RendererStyleFlags2,
15
+ TemplateRef
16
+ } from '@angular/core';
17
+ import { CommonModule, DOCUMENT } from '@angular/common';
18
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms';
19
+ import { Inject } from '@angular/core';
20
+ import { OptionComponent } from '../option/option.component';
21
+
22
+ // Directive markers for prefix/suffix
23
+ import { Directive } from '@angular/core';
24
+
25
+ @Directive({
26
+ selector: '[prefix]',
27
+ standalone: true
28
+ })
29
+ export class SelectPrefixDirective {}
30
+
31
+ @Directive({
32
+ selector: '[suffix]',
33
+ standalone: true
34
+ })
35
+ export class SelectSuffixDirective {}
36
+
37
+ export interface SelectOption {
38
+ value: any;
39
+ label: string;
40
+ disabled?: boolean;
41
+ }
42
+
43
+ interface DropdownPosition {
44
+ top?: string;
45
+ bottom?: string;
46
+ left: string;
47
+ width: string;
48
+ maxHeight: string;
49
+ }
50
+
51
+ @Component({
52
+ selector: 'app-select',
53
+ standalone: true,
54
+ imports: [CommonModule, ReactiveFormsModule, FormsModule, SelectPrefixDirective, SelectSuffixDirective],
55
+ template: `
56
+ <div class="select-wrapper" [class.select-wrapper--unstyled]="unstyled" [ngClass]="customClass">
57
+ <label *ngIf="label && !unstyled" [for]="id">{{ label }}</label>
58
+ <div class="select-container" [class.select-container--unstyled]="unstyled">
59
+ <div
60
+ #trigger
61
+ class="select-header"
62
+ [attr.tabindex]="disabled ? -1 : 0"
63
+ [class.error]="hasError && !unstyled"
64
+ [class.disabled]="disabled"
65
+ [class.open]="isOpen"
66
+ [class.select-header--unstyled]="unstyled"
67
+ [class.select-header--has-prefix]="hasPrefix"
68
+ (blur)="handleBlur()"
69
+ (click)="!disabled && toggleDropdown($event)">
70
+ <span class="select-prefix" *ngIf="hasPrefix">
71
+ <ng-content select="[prefix]"></ng-content>
72
+ </span>
73
+ <span class="select-placeholder" *ngIf="!getSelectedLabel()">
74
+ {{ placeholder }}
75
+ </span>
76
+ <span class="select-selected" *ngIf="getSelectedLabel()">
77
+ {{ getSelectedLabel() }}
78
+ </span>
79
+ <span class="select-suffix" *ngIf="hasSuffix">
80
+ <ng-content select="[suffix]"></ng-content>
81
+ </span>
82
+ <svg class="select-arrow" [class.open]="isOpen" width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
83
+ <path d="M1 1.5L6 6.5L11 1.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
84
+ </svg>
85
+ </div>
86
+ </div>
87
+ <div *ngIf="hasError && errorMessage && !unstyled" class="field-error">
88
+ {{ errorMessage }}
89
+ </div>
90
+
91
+ <!-- Hidden container for projected options -->
92
+ <div style="display: none;">
93
+ <ng-content></ng-content>
94
+ </div>
95
+ </div>
96
+ `,
97
+ providers: [
98
+ {
99
+ provide: NG_VALUE_ACCESSOR,
100
+ useExisting: forwardRef(() => SelectComponent),
101
+ multi: true
102
+ }
103
+ ]
104
+ })
105
+ export class SelectComponent implements ControlValueAccessor, OnDestroy, AfterContentInit {
106
+ @Input() label = '';
107
+ @Input() id = '';
108
+ @Input() placeholder = '';
109
+ @Input() options: SelectOption[] | any[] = [];
110
+ @Input() hasError = false;
111
+ @Input() errorMessage = '';
112
+ @Input() disabled = false;
113
+ @Input() unstyled = false;
114
+ @Input() displayKey: string = 'label'; // Property to display as label
115
+ @Input() valueKey: string = 'value'; // Property to use as value
116
+ @Input() customClass: string = ''; // Custom CSS class for styling
117
+ @ViewChild('trigger', { read: ElementRef }) trigger!: ElementRef;
118
+ @ContentChildren(OptionComponent) contentOptions!: QueryList<OptionComponent>;
119
+ @ContentChild(SelectPrefixDirective) prefixDirective!: SelectPrefixDirective;
120
+ @ContentChild(SelectSuffixDirective) suffixDirective!: SelectSuffixDirective;
121
+
122
+ get hasPrefix(): boolean {
123
+ return !!this.prefixDirective;
124
+ }
125
+
126
+ get hasSuffix(): boolean {
127
+ return !!this.suffixDirective;
128
+ }
129
+
130
+ value: any = '';
131
+ isOpen = false;
132
+ onChange: any = () => {};
133
+ onTouched: any = () => {};
134
+ private _usingContentOptions = false;
135
+
136
+ private dropdownElement: HTMLElement | null = null;
137
+ private backdropElement: HTMLElement | null = null;
138
+ private scrollHandler: (() => void) | null = null;
139
+ private resizeHandler: (() => void) | null = null;
140
+
141
+ constructor(
142
+ private renderer: Renderer2,
143
+ @Inject(DOCUMENT) private document: Document
144
+ ) {}
145
+
146
+ ngAfterContentInit(): void {
147
+ // Determine if using content projection or input options
148
+ this._usingContentOptions = this.contentOptions && this.contentOptions.length > 0;
149
+
150
+ // Subscribe to changes in content options
151
+ if (this.contentOptions) {
152
+ this.contentOptions.changes.subscribe(() => {
153
+ this._usingContentOptions = this.contentOptions.length > 0;
154
+ });
155
+ }
156
+ }
157
+
158
+ ngOnDestroy(): void {
159
+ this.destroyDropdown();
160
+ }
161
+
162
+ @HostListener('keydown.escape')
163
+ onEsc() {
164
+ this.closeDropdown();
165
+ this.onTouched();
166
+ }
167
+
168
+ handleBlur() {
169
+ this.onTouched();
170
+ }
171
+
172
+ /**
173
+ * Get all options (from content or input)
174
+ */
175
+ private getAllOptions(): any[] {
176
+ if (this._usingContentOptions && this.contentOptions) {
177
+ return this.contentOptions.toArray();
178
+ }
179
+ return this.options || [];
180
+ }
181
+
182
+ /**
183
+ * Get display label from option
184
+ */
185
+ getOptionLabel(option: any): string {
186
+ if (option instanceof OptionComponent) {
187
+ return option.viewValue;
188
+ }
189
+ return option?.[this.displayKey] || '';
190
+ }
191
+
192
+ /**
193
+ * Get value from option
194
+ */
195
+ getOptionValue(option: any): any {
196
+ if (option instanceof OptionComponent) {
197
+ return option.value;
198
+ }
199
+ return option?.[this.valueKey];
200
+ }
201
+
202
+ /**
203
+ * Check if option is disabled
204
+ */
205
+ isOptionDisabled(option: any): boolean {
206
+ if (option instanceof OptionComponent) {
207
+ return option.disabled;
208
+ }
209
+ return option?.disabled || false;
210
+ }
211
+
212
+ @HostListener('document:click', ['$event'])
213
+ onDocumentClick(event: MouseEvent): void {
214
+ // Close dropdown if click is outside
215
+ if (this.isOpen && this.dropdownElement && !this.dropdownElement.contains(event.target as Node)) {
216
+ const triggerElement = this.trigger?.nativeElement;
217
+ if (triggerElement && !triggerElement.contains(event.target as Node)) {
218
+ this.closeDropdown();
219
+ }
220
+ }
221
+ }
222
+
223
+ toggleDropdown(event: Event): void {
224
+ event.stopPropagation();
225
+ if (this.disabled) return;
226
+
227
+ if (this.isOpen) {
228
+ this.closeDropdown();
229
+ } else {
230
+ this.openDropdown();
231
+ }
232
+ }
233
+
234
+ private openDropdown(): void {
235
+ if (!this.trigger) return;
236
+
237
+ // Create backdrop
238
+ this.backdropElement = this.renderer.createElement('div');
239
+ this.renderer.addClass(this.backdropElement, 'select-custom-backdrop');
240
+ this.renderer.listen(this.backdropElement, 'click', () => this.closeDropdown());
241
+ this.renderer.appendChild(this.document.body, this.backdropElement);
242
+
243
+ // Create dropdown
244
+ this.dropdownElement = this.renderer.createElement('div');
245
+ this.renderer.addClass(this.dropdownElement, 'select-custom-dropdown');
246
+ this.renderer.addClass(this.dropdownElement, 'select-overlay-pane');
247
+
248
+ // Calculate position
249
+ const position = this.calculatePosition();
250
+
251
+ // Apply positioning styles
252
+ this.renderer.setStyle(this.dropdownElement, 'position', 'fixed');
253
+ this.renderer.setStyle(this.dropdownElement, 'left', position.left);
254
+ this.renderer.setStyle(this.dropdownElement, 'width', position.width);
255
+ this.renderer.setStyle(this.dropdownElement, 'max-height', position.maxHeight);
256
+ this.renderer.setStyle(this.dropdownElement, 'z-index', '100000', RendererStyleFlags2.Important);
257
+
258
+ if (position.top) {
259
+ this.renderer.setStyle(this.dropdownElement, 'top', position.top);
260
+ } else if (position.bottom) {
261
+ this.renderer.setStyle(this.dropdownElement, 'bottom', position.bottom);
262
+ }
263
+
264
+ // Create dropdown content wrapper
265
+ const dropdownContent = this.renderer.createElement('div');
266
+ this.renderer.addClass(dropdownContent, 'select-dropdown');
267
+
268
+ // Add options
269
+ const allOptions = this.getAllOptions();
270
+ allOptions.forEach(option => {
271
+ const optionElement = this.createOptionElement(option);
272
+ this.renderer.appendChild(dropdownContent, optionElement);
273
+ });
274
+
275
+ this.renderer.appendChild(this.dropdownElement, dropdownContent);
276
+ this.renderer.appendChild(this.document.body, this.dropdownElement);
277
+
278
+ this.isOpen = true;
279
+
280
+ // Add scroll and resize listeners to reposition dropdown
281
+ this.addRepositionListeners();
282
+
283
+ // Trigger animation
284
+ setTimeout(() => {
285
+ if (this.dropdownElement) {
286
+ this.renderer.addClass(this.dropdownElement, 'select-dropdown-open');
287
+ }
288
+ }, 10);
289
+ }
290
+
291
+ private calculatePosition(): DropdownPosition {
292
+ const triggerElement = this.trigger.nativeElement;
293
+ const rect = triggerElement.getBoundingClientRect();
294
+
295
+ const spaceBelow = window.innerHeight - rect.bottom;
296
+ const spaceAbove = rect.top;
297
+ const dropdownMaxHeight = 300;
298
+ const offsetY = 4;
299
+
300
+ const shouldOpenAbove = spaceBelow < 200 && spaceAbove > spaceBelow;
301
+
302
+ const position: DropdownPosition = {
303
+ left: `${rect.left}px`,
304
+ width: `${rect.width}px`,
305
+ maxHeight: `${dropdownMaxHeight}px`
306
+ };
307
+
308
+ if (shouldOpenAbove) {
309
+ position.bottom = `${window.innerHeight - rect.top + offsetY}px`;
310
+ } else {
311
+ position.top = `${rect.bottom + offsetY}px`;
312
+ }
313
+
314
+ return position;
315
+ }
316
+
317
+ private addRepositionListeners(): void {
318
+ // Create handlers
319
+ const scrollHandler = () => this.updateDropdownPosition();
320
+ const resizeHandler = () => this.updateDropdownPosition();
321
+
322
+ // Add scroll listener to window with capture phase to catch all scroll events
323
+ window.addEventListener('scroll', scrollHandler, true);
324
+ this.scrollHandler = () => window.removeEventListener('scroll', scrollHandler, true);
325
+
326
+ // Add resize listener
327
+ window.addEventListener('resize', resizeHandler);
328
+ this.resizeHandler = () => window.removeEventListener('resize', resizeHandler);
329
+ }
330
+
331
+ private updateDropdownPosition(): void {
332
+ if (!this.dropdownElement || !this.trigger) return;
333
+
334
+ const position = this.calculatePosition();
335
+
336
+ // Update position styles
337
+ this.renderer.setStyle(this.dropdownElement, 'left', position.left);
338
+ this.renderer.setStyle(this.dropdownElement, 'width', position.width);
339
+
340
+ // Remove old top/bottom styles
341
+ this.renderer.removeStyle(this.dropdownElement, 'top');
342
+ this.renderer.removeStyle(this.dropdownElement, 'bottom');
343
+
344
+ // Apply new top/bottom position
345
+ if (position.top) {
346
+ this.renderer.setStyle(this.dropdownElement, 'top', position.top);
347
+ } else if (position.bottom) {
348
+ this.renderer.setStyle(this.dropdownElement, 'bottom', position.bottom);
349
+ }
350
+ }
351
+
352
+ private createOptionElement(option: any): HTMLElement {
353
+ const optionElement = this.renderer.createElement('div');
354
+ this.renderer.addClass(optionElement, 'select-option');
355
+
356
+ const optionValue = this.getOptionValue(option);
357
+ const optionLabel = this.getOptionLabel(option);
358
+ const optionDisabled = this.isOptionDisabled(option);
359
+
360
+ if (this.isEmptyValue(optionValue)) {
361
+ this.renderer.addClass(optionElement, 'select-option-placeholder');
362
+ }
363
+
364
+ if (this.isSelected(optionValue)) {
365
+ this.renderer.addClass(optionElement, 'selected');
366
+ }
367
+
368
+ if (optionDisabled) {
369
+ this.renderer.addClass(optionElement, 'disabled');
370
+ }
371
+
372
+ // Option text
373
+ const textSpan = this.renderer.createElement('span');
374
+ this.renderer.addClass(textSpan, 'option-text');
375
+ const text = this.renderer.createText(optionLabel);
376
+ this.renderer.appendChild(textSpan, text);
377
+ this.renderer.appendChild(optionElement, textSpan);
378
+
379
+ // Check icon for selected option
380
+ if (this.isSelected(optionValue)) {
381
+ const svg = this.createCheckIcon();
382
+ this.renderer.appendChild(optionElement, svg);
383
+ }
384
+
385
+ // Click handler
386
+ if (!optionDisabled) {
387
+ this.renderer.listen(optionElement, 'click', (event: Event) => {
388
+ event.stopPropagation();
389
+ this.selectOption(option);
390
+ });
391
+ }
392
+
393
+ return optionElement;
394
+ }
395
+
396
+ private createCheckIcon(): SVGElement {
397
+ const svg = this.renderer.createElement('svg', 'svg');
398
+ this.renderer.addClass(svg, 'check-icon');
399
+ this.renderer.setAttribute(svg, 'width', '14');
400
+ this.renderer.setAttribute(svg, 'height', '10');
401
+ this.renderer.setAttribute(svg, 'viewBox', '0 0 10 8');
402
+ this.renderer.setAttribute(svg, 'fill', 'none');
403
+ this.renderer.setAttribute(svg, 'xmlns', 'http://www.w3.org/2000/svg');
404
+
405
+ const path = this.renderer.createElement('path', 'svg');
406
+ this.renderer.setAttribute(path, 'd', 'M9 1L3.5 6.5L1 4');
407
+ this.renderer.setAttribute(path, 'stroke', '#F59100');
408
+ this.renderer.setAttribute(path, 'stroke-width', '1.6666');
409
+ this.renderer.setAttribute(path, 'stroke-linecap', 'round');
410
+ this.renderer.setAttribute(path, 'stroke-linejoin', 'round');
411
+
412
+ this.renderer.appendChild(svg, path);
413
+ return svg;
414
+ }
415
+
416
+ closeDropdown(): void {
417
+ this.destroyDropdown();
418
+ this.isOpen = false;
419
+ }
420
+
421
+ private destroyDropdown(): void {
422
+ // Remove listeners
423
+ if (this.scrollHandler) {
424
+ this.scrollHandler();
425
+ this.scrollHandler = null;
426
+ }
427
+
428
+ if (this.resizeHandler) {
429
+ this.resizeHandler();
430
+ this.resizeHandler = null;
431
+ }
432
+
433
+ if (this.dropdownElement) {
434
+ this.renderer.removeChild(this.document.body, this.dropdownElement);
435
+ this.dropdownElement = null;
436
+ }
437
+
438
+ if (this.backdropElement) {
439
+ this.renderer.removeChild(this.document.body, this.backdropElement);
440
+ this.backdropElement = null;
441
+ }
442
+ }
443
+
444
+ selectOption(option: any): void {
445
+ if (this.isOptionDisabled(option)) return;
446
+
447
+ this.value = this.getOptionValue(option);
448
+ this.onChange(this.value);
449
+ this.onTouched();
450
+ this.closeDropdown();
451
+ }
452
+
453
+ isSelected(value: any): boolean {
454
+ return this.value === value;
455
+ }
456
+
457
+ isEmptyValue(value: any): boolean {
458
+ return value === '' || value === null || value === undefined;
459
+ }
460
+
461
+ getSelectedLabel(): string {
462
+ const allOptions = this.getAllOptions();
463
+ const selectedOption = allOptions.find(opt => this.getOptionValue(opt) === this.value);
464
+ return selectedOption ? this.getOptionLabel(selectedOption) : '';
465
+ }
466
+
467
+ writeValue(value: any): void {
468
+ this.value = value || '';
469
+ }
470
+
471
+ registerOnChange(fn: any): void {
472
+ this.onChange = fn;
473
+ }
474
+
475
+ registerOnTouched(fn: any): void {
476
+ this.onTouched = fn;
477
+ }
478
+
479
+ setDisabledState(isDisabled: boolean): void {
480
+ this.disabled = isDisabled;
481
+ }
482
+ }
@@ -0,0 +1,201 @@
1
+ .snackbar-container {
2
+ position: fixed;
3
+ z-index: 999999;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 12px;
7
+ max-width: 644px;
8
+ pointer-events: none;
9
+
10
+ &--top-right {
11
+ top: 24px;
12
+ right: 24px;
13
+ }
14
+
15
+ &--top-left {
16
+ top: 24px;
17
+ left: 24px;
18
+ }
19
+
20
+ &--top-center {
21
+ top: 24px;
22
+ left: 50%;
23
+ transform: translateX(-50%);
24
+ }
25
+
26
+ &--bottom-right {
27
+ bottom: 24px;
28
+ right: 24px;
29
+ }
30
+
31
+ &--bottom-left {
32
+ bottom: 24px;
33
+ left: 24px;
34
+ }
35
+
36
+ &--bottom-center {
37
+ bottom: 24px;
38
+ left: 50%;
39
+ transform: translateX(-50%);
40
+ }
41
+
42
+ @media (max-width: 640px) {
43
+ left: 16px !important;
44
+ right: 16px !important;
45
+ max-width: none;
46
+ transform: none !important;
47
+
48
+ &--top-right,
49
+ &--top-left,
50
+ &--top-center {
51
+ top: 16px;
52
+ }
53
+
54
+ &--bottom-right,
55
+ &--bottom-left,
56
+ &--bottom-center {
57
+ bottom: 16px;
58
+ }
59
+ }
60
+ }
61
+
62
+ .snackbar {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 12px;
66
+ padding: 16px;
67
+ border-radius: 8px;
68
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
69
+ background: #ffffff;
70
+ pointer-events: auto;
71
+ min-width: 300px;
72
+ max-width: 644px;
73
+ animation: slideIn 300ms ease-out;
74
+
75
+ @keyframes slideIn {
76
+ from {
77
+ transform: translateY(-100%);
78
+ opacity: 0;
79
+ }
80
+ to {
81
+ transform: translateY(0);
82
+ opacity: 1;
83
+ }
84
+ }
85
+
86
+ @media (max-width: 640px) {
87
+ min-width: auto;
88
+ max-width: none;
89
+ }
90
+
91
+ &--success {
92
+ background: #f0fdf4;
93
+ border: 1px solid #22c55e;
94
+
95
+ .snackbar__icon {
96
+ color: #22c55e;
97
+ }
98
+ }
99
+
100
+ &--error {
101
+ background: #FEF3F2;
102
+ border: 1px solid #F04438;
103
+
104
+ .snackbar__icon {
105
+ color: #F04438;
106
+ }
107
+ }
108
+
109
+ &--warning {
110
+ background: #fffbeb;
111
+ border: 1px solid #f59e0b;
112
+
113
+ .snackbar__icon {
114
+ color: #f59e0b;
115
+ }
116
+ }
117
+
118
+ &--info {
119
+ background: #eff6ff;
120
+ border: 1px solid #3b82f6;
121
+
122
+ .snackbar__icon {
123
+ color: #3b82f6;
124
+ }
125
+ }
126
+
127
+ &__icon {
128
+ flex-shrink: 0;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ width: 20px;
133
+ height: 20px;
134
+ }
135
+
136
+ &__content {
137
+ flex: 1;
138
+ min-width: 0;
139
+ }
140
+
141
+ &__message {
142
+ margin: 0;
143
+ line-height: 20px;
144
+ word-wrap: break-word;
145
+ font-family: Quicksand, sans-serif;
146
+ font-weight: 600;
147
+ font-size: 14px;
148
+ color: #2E353A;
149
+ }
150
+
151
+ &__action {
152
+ flex-shrink: 0;
153
+ padding: 6px 12px;
154
+ border: none;
155
+ border-radius: 4px;
156
+ background: transparent;
157
+ color: inherit;
158
+ font-size: 14px;
159
+ font-weight: 600;
160
+ cursor: pointer;
161
+ transition: background-color 0.2s;
162
+
163
+ &:hover {
164
+ background: rgba(0, 0, 0, 0.05);
165
+ }
166
+
167
+ &:active {
168
+ background: rgba(0, 0, 0, 0.1);
169
+ }
170
+ }
171
+
172
+ &__close {
173
+ flex-shrink: 0;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ width: 24px;
178
+ height: 24px;
179
+ padding: 0;
180
+ border: none;
181
+ border-radius: 4px;
182
+ background: transparent;
183
+ color: #2E353A;
184
+ cursor: pointer;
185
+ transition: all 0.2s;
186
+
187
+ &:hover {
188
+ background: rgba(0, 0, 0, 0.05);
189
+ color: #1f2937;
190
+ }
191
+
192
+ &:active {
193
+ background: rgba(0, 0, 0, 0.1);
194
+ }
195
+
196
+ svg {
197
+ width: 14px;
198
+ height: 14px;
199
+ }
200
+ }
201
+ }