ng-comps 1.0.2 → 2.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 (197) hide show
  1. package/fesm2022/ng-comps.mjs +4254 -0
  2. package/package.json +54 -58
  3. package/src/styles.css +54 -0
  4. package/types/ng-comps.d.ts +1348 -0
  5. package/.editorconfig +0 -17
  6. package/.github/copilot-instructions.md +0 -55
  7. package/.github/workflows/ci.yml +0 -29
  8. package/.prettierrc +0 -12
  9. package/.storybook/main.ts +0 -21
  10. package/.storybook/preview.ts +0 -27
  11. package/.storybook/tsconfig.doc.json +0 -10
  12. package/.storybook/tsconfig.json +0 -15
  13. package/.storybook/typings.d.ts +0 -4
  14. package/.vscode/extensions.json +0 -4
  15. package/.vscode/launch.json +0 -20
  16. package/.vscode/mcp.json +0 -9
  17. package/.vscode/tasks.json +0 -42
  18. package/ACCESSIBILITY.md +0 -127
  19. package/angular.json +0 -106
  20. package/documentation.json +0 -13394
  21. package/ng-package.json +0 -27
  22. package/public/favicon.ico +0 -0
  23. package/scripts/prepare-package.mjs +0 -80
  24. package/src/app/a11y/accessibility.utils.ts +0 -35
  25. package/src/app/a11y/index.ts +0 -6
  26. package/src/app/accessibility/ng-comps.a11y.spec.ts +0 -108
  27. package/src/app/app.config.ts +0 -11
  28. package/src/app/app.css +0 -107
  29. package/src/app/app.html +0 -48
  30. package/src/app/app.routes.ts +0 -3
  31. package/src/app/app.spec.ts +0 -23
  32. package/src/app/app.ts +0 -10
  33. package/src/app/components/accordion/index.ts +0 -2
  34. package/src/app/components/accordion/mf-accordion.component.css +0 -38
  35. package/src/app/components/accordion/mf-accordion.component.spec.ts +0 -48
  36. package/src/app/components/accordion/mf-accordion.component.ts +0 -53
  37. package/src/app/components/alert/index.ts +0 -2
  38. package/src/app/components/alert/mf-alert.component.css +0 -100
  39. package/src/app/components/alert/mf-alert.component.spec.ts +0 -59
  40. package/src/app/components/alert/mf-alert.component.ts +0 -68
  41. package/src/app/components/autocomplete/index.ts +0 -5
  42. package/src/app/components/autocomplete/mf-autocomplete.component.css +0 -105
  43. package/src/app/components/autocomplete/mf-autocomplete.component.spec.ts +0 -116
  44. package/src/app/components/autocomplete/mf-autocomplete.component.ts +0 -307
  45. package/src/app/components/avatar/index.ts +0 -2
  46. package/src/app/components/avatar/mf-avatar.component.css +0 -27
  47. package/src/app/components/avatar/mf-avatar.component.spec.ts +0 -49
  48. package/src/app/components/avatar/mf-avatar.component.ts +0 -99
  49. package/src/app/components/badge/index.ts +0 -2
  50. package/src/app/components/badge/mf-badge.component.css +0 -32
  51. package/src/app/components/badge/mf-badge.component.spec.ts +0 -40
  52. package/src/app/components/badge/mf-badge.component.ts +0 -105
  53. package/src/app/components/breadcrumb/index.ts +0 -2
  54. package/src/app/components/breadcrumb/mf-breadcrumb.component.css +0 -61
  55. package/src/app/components/breadcrumb/mf-breadcrumb.component.spec.ts +0 -61
  56. package/src/app/components/breadcrumb/mf-breadcrumb.component.ts +0 -75
  57. package/src/app/components/button/index.ts +0 -2
  58. package/src/app/components/button/mf-button.component.css +0 -136
  59. package/src/app/components/button/mf-button.component.ts +0 -174
  60. package/src/app/components/card/index.ts +0 -2
  61. package/src/app/components/card/mf-card.component.css +0 -82
  62. package/src/app/components/card/mf-card.component.ts +0 -59
  63. package/src/app/components/checkbox/index.ts +0 -1
  64. package/src/app/components/checkbox/mf-checkbox.component.css +0 -75
  65. package/src/app/components/checkbox/mf-checkbox.component.ts +0 -187
  66. package/src/app/components/chip/index.ts +0 -2
  67. package/src/app/components/chip/mf-chip.component.css +0 -69
  68. package/src/app/components/chip/mf-chip.component.spec.ts +0 -47
  69. package/src/app/components/chip/mf-chip.component.ts +0 -77
  70. package/src/app/components/datepicker/index.ts +0 -2
  71. package/src/app/components/datepicker/mf-datepicker.component.css +0 -102
  72. package/src/app/components/datepicker/mf-datepicker.component.spec.ts +0 -69
  73. package/src/app/components/datepicker/mf-datepicker.component.ts +0 -233
  74. package/src/app/components/dialog/index.ts +0 -3
  75. package/src/app/components/dialog/mf-dialog.component.css +0 -73
  76. package/src/app/components/dialog/mf-dialog.component.ts +0 -160
  77. package/src/app/components/dialog/mf-dialog.service.spec.ts +0 -61
  78. package/src/app/components/dialog/mf-dialog.service.ts +0 -52
  79. package/src/app/components/divider/index.ts +0 -2
  80. package/src/app/components/divider/mf-divider.component.css +0 -38
  81. package/src/app/components/divider/mf-divider.component.spec.ts +0 -40
  82. package/src/app/components/divider/mf-divider.component.ts +0 -44
  83. package/src/app/components/form-field/index.ts +0 -1
  84. package/src/app/components/form-field/mf-form-field.component.css +0 -51
  85. package/src/app/components/form-field/mf-form-field.component.ts +0 -74
  86. package/src/app/components/grid-list/index.ts +0 -2
  87. package/src/app/components/grid-list/mf-grid-list.component.css +0 -47
  88. package/src/app/components/grid-list/mf-grid-list.component.spec.ts +0 -57
  89. package/src/app/components/grid-list/mf-grid-list.component.ts +0 -68
  90. package/src/app/components/icon/index.ts +0 -2
  91. package/src/app/components/icon/mf-icon.component.css +0 -56
  92. package/src/app/components/icon/mf-icon.component.ts +0 -41
  93. package/src/app/components/input/index.ts +0 -2
  94. package/src/app/components/input/mf-input.component.css +0 -105
  95. package/src/app/components/input/mf-input.component.ts +0 -217
  96. package/src/app/components/menu/index.ts +0 -2
  97. package/src/app/components/menu/mf-menu.component.css +0 -31
  98. package/src/app/components/menu/mf-menu.component.spec.ts +0 -49
  99. package/src/app/components/menu/mf-menu.component.ts +0 -66
  100. package/src/app/components/paginator/index.ts +0 -1
  101. package/src/app/components/paginator/mf-paginator.component.css +0 -32
  102. package/src/app/components/paginator/mf-paginator.component.spec.ts +0 -44
  103. package/src/app/components/paginator/mf-paginator.component.ts +0 -52
  104. package/src/app/components/progress-bar/index.ts +0 -2
  105. package/src/app/components/progress-bar/mf-progress-bar.component.css +0 -53
  106. package/src/app/components/progress-bar/mf-progress-bar.component.spec.ts +0 -65
  107. package/src/app/components/progress-bar/mf-progress-bar.component.ts +0 -79
  108. package/src/app/components/progress-spinner/index.ts +0 -2
  109. package/src/app/components/progress-spinner/mf-progress-spinner.component.css +0 -38
  110. package/src/app/components/progress-spinner/mf-progress-spinner.component.spec.ts +0 -59
  111. package/src/app/components/progress-spinner/mf-progress-spinner.component.ts +0 -81
  112. package/src/app/components/radio-button/index.ts +0 -2
  113. package/src/app/components/radio-button/mf-radio-button.component.css +0 -86
  114. package/src/app/components/radio-button/mf-radio-button.component.spec.ts +0 -55
  115. package/src/app/components/radio-button/mf-radio-button.component.ts +0 -219
  116. package/src/app/components/select/index.ts +0 -2
  117. package/src/app/components/select/mf-select.component.css +0 -121
  118. package/src/app/components/select/mf-select.component.spec.ts +0 -108
  119. package/src/app/components/select/mf-select.component.ts +0 -252
  120. package/src/app/components/sidenav/index.ts +0 -2
  121. package/src/app/components/sidenav/mf-sidenav.component.css +0 -168
  122. package/src/app/components/sidenav/mf-sidenav.component.spec.ts +0 -57
  123. package/src/app/components/sidenav/mf-sidenav.component.ts +0 -126
  124. package/src/app/components/slide-toggle/index.ts +0 -1
  125. package/src/app/components/slide-toggle/mf-slide-toggle.component.css +0 -42
  126. package/src/app/components/slide-toggle/mf-slide-toggle.component.spec.ts +0 -43
  127. package/src/app/components/slide-toggle/mf-slide-toggle.component.ts +0 -188
  128. package/src/app/components/snackbar/index.ts +0 -2
  129. package/src/app/components/snackbar/mf-snackbar.service.css +0 -31
  130. package/src/app/components/snackbar/mf-snackbar.service.spec.ts +0 -81
  131. package/src/app/components/snackbar/mf-snackbar.service.ts +0 -77
  132. package/src/app/components/table/index.ts +0 -2
  133. package/src/app/components/table/mf-table.component.css +0 -68
  134. package/src/app/components/table/mf-table.component.spec.ts +0 -76
  135. package/src/app/components/table/mf-table.component.ts +0 -117
  136. package/src/app/components/tabs/index.ts +0 -2
  137. package/src/app/components/tabs/mf-tabs.component.css +0 -31
  138. package/src/app/components/tabs/mf-tabs.component.spec.ts +0 -50
  139. package/src/app/components/tabs/mf-tabs.component.ts +0 -62
  140. package/src/app/components/textarea/index.ts +0 -2
  141. package/src/app/components/textarea/mf-textarea.component.css +0 -48
  142. package/src/app/components/textarea/mf-textarea.component.spec.ts +0 -55
  143. package/src/app/components/textarea/mf-textarea.component.ts +0 -227
  144. package/src/app/components/toolbar/index.ts +0 -2
  145. package/src/app/components/toolbar/mf-toolbar.component.css +0 -77
  146. package/src/app/components/toolbar/mf-toolbar.component.ts +0 -56
  147. package/src/app/components/tooltip/index.ts +0 -3
  148. package/src/app/components/tooltip/mf-tooltip.component.css +0 -7
  149. package/src/app/components/tooltip/mf-tooltip.component.spec.ts +0 -37
  150. package/src/app/components/tooltip/mf-tooltip.component.ts +0 -47
  151. package/src/app/components/tooltip/mf-tooltip.directive.ts +0 -22
  152. package/src/index.html +0 -18
  153. package/src/main.ts +0 -6
  154. package/src/public-api.ts +0 -31
  155. package/src/stories/About.mdx +0 -72
  156. package/src/stories/Accessibility.mdx +0 -59
  157. package/src/stories/Welcome.mdx +0 -26
  158. package/src/stories/assets/accessibility.png +0 -0
  159. package/src/stories/assets/accessibility.svg +0 -1
  160. package/src/stories/assets/addon-library.png +0 -0
  161. package/src/stories/assets/assets.png +0 -0
  162. package/src/stories/assets/avif-test-image.avif +0 -0
  163. package/src/stories/assets/context.png +0 -0
  164. package/src/stories/assets/discord.svg +0 -1
  165. package/src/stories/assets/docs.png +0 -0
  166. package/src/stories/assets/figma-plugin.png +0 -0
  167. package/src/stories/assets/github.svg +0 -1
  168. package/src/stories/assets/share.png +0 -0
  169. package/src/stories/assets/styling.png +0 -0
  170. package/src/stories/assets/testing.png +0 -0
  171. package/src/stories/assets/theming.png +0 -0
  172. package/src/stories/assets/tutorials.svg +0 -1
  173. package/src/stories/assets/youtube.svg +0 -1
  174. package/src/stories/mf-a11y-contracts.stories.ts +0 -472
  175. package/src/stories/mf-autocomplete.stories.ts +0 -194
  176. package/src/stories/mf-button.stories.ts +0 -152
  177. package/src/stories/mf-card.stories.ts +0 -147
  178. package/src/stories/mf-checkbox.stories.ts +0 -88
  179. package/src/stories/mf-datepicker.stories.ts +0 -118
  180. package/src/stories/mf-dialog.stories.ts +0 -159
  181. package/src/stories/mf-form-field.stories.ts +0 -108
  182. package/src/stories/mf-grid-list.stories.ts +0 -104
  183. package/src/stories/mf-icon.stories.ts +0 -133
  184. package/src/stories/mf-input.stories.ts +0 -158
  185. package/src/stories/mf-menu.stories.ts +0 -71
  186. package/src/stories/mf-progress-bar.stories.ts +0 -119
  187. package/src/stories/mf-progress-spinner.stories.ts +0 -124
  188. package/src/stories/mf-radio-button.stories.ts +0 -111
  189. package/src/stories/mf-select.stories.ts +0 -184
  190. package/src/stories/mf-sidenav.stories.ts +0 -331
  191. package/src/stories/mf-table.stories.ts +0 -80
  192. package/src/stories/mf-toolbar.stories.ts +0 -112
  193. package/src/stories/user.ts +0 -3
  194. package/tsconfig.app.json +0 -15
  195. package/tsconfig.json +0 -33
  196. package/tsconfig.spec.json +0 -15
  197. package/vercel.json +0 -6
@@ -1,57 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { MfSidenavComponent } from './mf-sidenav.component';
3
- import { NoopAnimationsModule } from '@angular/platform-browser/animations';
4
-
5
- describe('MfSidenavComponent', () => {
6
- let fixture: ComponentFixture<MfSidenavComponent>;
7
- let component: MfSidenavComponent;
8
-
9
- beforeEach(async () => {
10
- await TestBed.configureTestingModule({
11
- imports: [MfSidenavComponent, NoopAnimationsModule],
12
- }).compileComponents();
13
-
14
- fixture = TestBed.createComponent(MfSidenavComponent);
15
- component = fixture.componentInstance;
16
- fixture.detectChanges();
17
- });
18
-
19
- it('should create', () => {
20
- expect(component).toBeTruthy();
21
- });
22
-
23
- it('should render mat-sidenav-container', () => {
24
- const container = fixture.nativeElement.querySelector('mat-sidenav-container');
25
- expect(container).toBeTruthy();
26
- });
27
-
28
- it('should render mat-sidenav', () => {
29
- const sidenav = fixture.nativeElement.querySelector('mat-sidenav');
30
- expect(sidenav).toBeTruthy();
31
- });
32
-
33
- it('should apply side class by default', () => {
34
- expect(component.sidenavClasses()).toContain('mf-sidenav--side');
35
- });
36
-
37
- it('should apply over class when mode is over', () => {
38
- fixture.componentRef.setInput('mode', 'over');
39
- expect(component.sidenavClasses()).toContain('mf-sidenav--over');
40
- });
41
-
42
- it('should apply push class when mode is push', () => {
43
- fixture.componentRef.setInput('mode', 'push');
44
- expect(component.sidenavClasses()).toContain('mf-sidenav--push');
45
- });
46
-
47
- it('should emit mfOpenedChange when sidenav state changes', () => {
48
- const spy = vi.fn();
49
- component.mfOpenedChange.subscribe(spy);
50
- fixture.componentRef.setInput('opened', true);
51
- fixture.detectChanges();
52
- });
53
-
54
- it('should apply container class', () => {
55
- expect(component.containerClasses()).toBe('mf-sidenav-container');
56
- });
57
- });
@@ -1,126 +0,0 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- Component,
4
- computed,
5
- input,
6
- output,
7
- } from '@angular/core';
8
- import { MatSidenavModule } from '@angular/material/sidenav';
9
- import { MatIconModule } from '@angular/material/icon';
10
-
11
- export type MfSidenavMode = 'over' | 'push' | 'side';
12
- export type MfSidenavPosition = 'start' | 'end';
13
-
14
- export interface MfSidenavNavItem {
15
- /** Nombre del icono Material (e.g. 'home', 'dashboard') */
16
- icon: string;
17
- /** Texto de la etiqueta */
18
- label: string;
19
- /** Identificador único del ítem */
20
- id: string;
21
- /** Ítem activo/seleccionado */
22
- active?: boolean;
23
- /** Deshabilitar el ítem */
24
- disabled?: boolean;
25
- /** Número de badge (0 oculta el badge) */
26
- badge?: number;
27
- }
28
-
29
- /**
30
- * Panel lateral de la librería ng-comps.
31
- * Envuelve Angular Material `mat-sidenav-container` y expone una API uniforme
32
- * con look and feel de marca.
33
- *
34
- * Dos formas de uso:
35
- * 1. **Navitems declarativos** — Proporciona `navItems`, `headerTitle` e icono.
36
- * 2. **Content projection** — Proyecta `[mfSidenavContent]` para control total.
37
- *
38
- * El contenido principal se proyecta sin atributo.
39
- */
40
- @Component({
41
- selector: 'mf-sidenav',
42
- imports: [MatSidenavModule, MatIconModule],
43
- template: `
44
- <mat-sidenav-container [class]="containerClasses()" [hasBackdrop]="hasBackdrop()">
45
- <mat-sidenav
46
- [class]="sidenavClasses()"
47
- [mode]="mode()"
48
- [position]="position()"
49
- [opened]="opened()"
50
- [style.width]="sidenavWidth()"
51
- (openedChange)="mfOpenedChange.emit($event)"
52
- >
53
- @if (navItems().length > 0) {
54
- <div class="mf-sidenav__nav">
55
- @if (headerTitle()) {
56
- <div class="mf-sidenav__header">
57
- @if (headerIcon()) {
58
- <mat-icon class="mf-sidenav__header-icon" aria-hidden="true">{{ headerIcon() }}</mat-icon>
59
- }
60
- <span class="mf-sidenav__header-title">{{ headerTitle() }}</span>
61
- </div>
62
- }
63
- <nav class="mf-sidenav__menu" [attr.aria-label]="navAriaLabel()">
64
- @for (item of navItems(); track item.id) {
65
- <button
66
- type="button"
67
- class="mf-sidenav__item"
68
- [class.mf-sidenav__item--active]="item.active"
69
- [class.mf-sidenav__item--disabled]="item.disabled"
70
- [disabled]="item.disabled ?? false"
71
- [attr.aria-current]="item.active ? 'page' : null"
72
- (click)="!item.disabled && mfNavItemClick.emit(item)"
73
- >
74
- <mat-icon class="mf-sidenav__item-icon" aria-hidden="true">{{ item.icon }}</mat-icon>
75
- <span class="mf-sidenav__item-label">{{ item.label }}</span>
76
- @if (item.badge && item.badge > 0) {
77
- <span class="mf-sidenav__item-badge" aria-label="{{ item.badge }} notifications">
78
- {{ item.badge > 99 ? '99+' : item.badge }}
79
- </span>
80
- }
81
- </button>
82
- }
83
- </nav>
84
- </div>
85
- } @else {
86
- <ng-content select="[mfSidenavContent]" />
87
- }
88
- </mat-sidenav>
89
- <mat-sidenav-content class="mf-sidenav__main">
90
- <ng-content />
91
- </mat-sidenav-content>
92
- </mat-sidenav-container>
93
- `,
94
- styleUrl: './mf-sidenav.component.css',
95
- changeDetection: ChangeDetectionStrategy.OnPush,
96
- })
97
- export class MfSidenavComponent {
98
- /** Abierto o cerrado */
99
- readonly opened = input(false);
100
- /** Modo de apertura */
101
- readonly mode = input<MfSidenavMode>('side');
102
- /** Posición del panel */
103
- readonly position = input<MfSidenavPosition>('start');
104
- /** Muestra backdrop al abrir */
105
- readonly hasBackdrop = input<boolean | null>(null);
106
- /** Ancho del panel lateral */
107
- readonly sidenavWidth = input('260px');
108
- /** Ítems de navegación declarativos */
109
- readonly navItems = input<MfSidenavNavItem[]>([]);
110
- /** Título de la cabecera del sidenav */
111
- readonly headerTitle = input<string | undefined>(undefined);
112
- /** Icono Material de la cabecera */
113
- readonly headerIcon = input<string | undefined>(undefined);
114
- /** Aria-label del elemento nav */
115
- readonly navAriaLabel = input('Primary navigation');
116
-
117
- readonly mfOpenedChange = output<boolean>();
118
- /** Emite el ítem de navegación pulsado */
119
- readonly mfNavItemClick = output<MfSidenavNavItem>();
120
-
121
- readonly containerClasses = computed(() => 'mf-sidenav-container');
122
-
123
- readonly sidenavClasses = computed(() => {
124
- return `mf-sidenav mf-sidenav--${this.mode()}`;
125
- });
126
- }
@@ -1 +0,0 @@
1
- export { MfSlideToggleComponent } from './mf-slide-toggle.component';
@@ -1,42 +0,0 @@
1
- :host {
2
- display: block;
3
- }
4
-
5
- .mf-slide-toggle {
6
- display: flex;
7
- flex-direction: column;
8
- gap: var(--mf-space-1);
9
- }
10
-
11
- .mf-slide-toggle .mat-mdc-slide-toggle {
12
- font-family: var(--mf-font-base) !important;
13
- font-size: var(--mf-text-sm) !important;
14
- }
15
-
16
- .mf-slide-toggle .mdc-switch--selected .mdc-switch__track::after {
17
- background-color: var(--mf-color-primary-200) !important;
18
- }
19
-
20
- .mf-slide-toggle .mdc-switch--selected .mdc-switch__handle::after {
21
- background-color: var(--mf-color-brand) !important;
22
- }
23
-
24
- .mf-slide-toggle--disabled {
25
- opacity: 0.42;
26
- }
27
-
28
- .mf-slide-toggle__hint,
29
- .mf-slide-toggle__error {
30
- margin: 0;
31
- padding-left: calc(36px + var(--mf-space-2));
32
- font-size: var(--mf-text-xs);
33
- line-height: var(--mf-leading-normal);
34
- }
35
-
36
- .mf-slide-toggle__hint {
37
- color: var(--mf-color-neutral-600);
38
- }
39
-
40
- .mf-slide-toggle__error {
41
- color: var(--mf-color-error-500);
42
- }
@@ -1,43 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { MfSlideToggleComponent } from './mf-slide-toggle.component';
3
- import { NoopAnimationsModule } from '@angular/platform-browser/animations';
4
-
5
- describe('MfSlideToggleComponent', () => {
6
- let fixture: ComponentFixture<MfSlideToggleComponent>;
7
- let component: MfSlideToggleComponent;
8
-
9
- beforeEach(async () => {
10
- await TestBed.configureTestingModule({
11
- imports: [MfSlideToggleComponent, NoopAnimationsModule],
12
- }).compileComponents();
13
-
14
- fixture = TestBed.createComponent(MfSlideToggleComponent);
15
- component = fixture.componentInstance;
16
- fixture.detectChanges();
17
- });
18
-
19
- it('should create', () => {
20
- expect(component).toBeTruthy();
21
- });
22
-
23
- it('should render mat-slide-toggle', () => {
24
- const toggle = fixture.nativeElement.querySelector('mat-slide-toggle');
25
- expect(toggle).toBeTruthy();
26
- });
27
-
28
- it('should apply base class', () => {
29
- expect(component.hostClasses()).toContain('mf-slide-toggle');
30
- });
31
-
32
- it('should apply disabled class when disabled', () => {
33
- fixture.componentRef.setInput('disabled', true);
34
- expect(component.hostClasses()).toContain('mf-slide-toggle--disabled');
35
- });
36
-
37
- it('should emit change event', () => {
38
- const spy = vi.fn();
39
- component.mfChange.subscribe(spy);
40
- component.onChange({ checked: true });
41
- expect(spy).toHaveBeenCalledWith(true);
42
- });
43
- });
@@ -1,188 +0,0 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- ChangeDetectorRef,
4
- Component,
5
- computed,
6
- effect,
7
- forwardRef,
8
- inject,
9
- input,
10
- output,
11
- signal,
12
- } from '@angular/core';
13
- import {
14
- ControlValueAccessor,
15
- NG_VALUE_ACCESSOR,
16
- NgControl,
17
- } from '@angular/forms';
18
- import { MatSlideToggleModule } from '@angular/material/slide-toggle';
19
- import {
20
- createUniqueId,
21
- hasAccessibleName,
22
- mergeAriaIds,
23
- warnInDev,
24
- } from '../../a11y';
25
-
26
- /**
27
- * Slide Toggle de la librería ng-comps.
28
- * Envuelve Angular Material `mat-slide-toggle` y expone una API uniforme
29
- * con look and feel de marca.
30
- */
31
- @Component({
32
- selector: 'mf-slide-toggle',
33
- imports: [MatSlideToggleModule],
34
- providers: [
35
- {
36
- provide: NG_VALUE_ACCESSOR,
37
- useExisting: forwardRef(() => MfSlideToggleComponent),
38
- multi: true,
39
- },
40
- ],
41
- template: `
42
- <div [class]="hostClasses()">
43
- <mat-slide-toggle
44
- [id]="controlId()"
45
- [checked]="internalValue()"
46
- [disabled]="isDisabled()"
47
- [required]="required()"
48
- [attr.aria-label]="resolvedAriaLabel()"
49
- [attr.aria-labelledby]="ariaLabelledby() || null"
50
- [attr.aria-describedby]="describedBy()"
51
- [attr.aria-invalid]="isInvalid() ? 'true' : null"
52
- [attr.aria-required]="required() ? 'true' : null"
53
- (change)="onToggleChange($event)"
54
- (blur)="onBlur()"
55
- >
56
- {{ label() }}
57
- </mat-slide-toggle>
58
-
59
- @if (hint()) {
60
- <p class="mf-slide-toggle__hint" [attr.id]="hintId()">{{ hint() }}</p>
61
- }
62
-
63
- @if (error()) {
64
- <p class="mf-slide-toggle__error" [attr.id]="errorId()" role="alert">
65
- {{ error() }}
66
- </p>
67
- }
68
- </div>
69
- `,
70
- styleUrl: './mf-slide-toggle.component.css',
71
- changeDetection: ChangeDetectionStrategy.OnPush,
72
- })
73
- export class MfSlideToggleComponent implements ControlValueAccessor {
74
- private readonly cdr = inject(ChangeDetectorRef);
75
- private readonly ngControl = inject(NgControl, { self: true, optional: true });
76
- private readonly generatedId = createUniqueId('mf-slide-toggle');
77
- private readonly disabledFromForm = signal(false);
78
- protected readonly internalValue = signal(false);
79
- private onControlChange: (value: boolean) => void = () => undefined;
80
- private onControlTouched: () => void = () => undefined;
81
-
82
- /** ID del control */
83
- readonly id = input<string | undefined>(undefined);
84
- /** Texto descriptivo */
85
- readonly label = input<string>('');
86
- /** Etiqueta accesible alternativa cuando no existe label visible */
87
- readonly ariaLabel = input<string | undefined>(undefined);
88
- /** Referencia externa a elementos que etiquetan el control */
89
- readonly ariaLabelledby = input<string | undefined>(undefined);
90
- /** Referencia externa a elementos descriptivos adicionales */
91
- readonly ariaDescribedby = input<string | undefined>(undefined);
92
- /** Estado activado */
93
- readonly checked = input(false);
94
- /** Deshabilitado */
95
- readonly disabled = input(false);
96
- /** Requerido */
97
- readonly required = input(false);
98
- /** Texto de ayuda */
99
- readonly hint = input<string | undefined>(undefined);
100
- /** Mensaje de error */
101
- readonly error = input<string | undefined>(undefined);
102
-
103
- readonly mfChange = output<boolean>();
104
-
105
- constructor() {
106
- effect(() => {
107
- this.internalValue.set(this.checked());
108
- });
109
-
110
- effect(() => {
111
- if (
112
- !hasAccessibleName(
113
- this.label(),
114
- this.ariaLabel(),
115
- this.ariaLabelledby(),
116
- )
117
- ) {
118
- warnInDev(
119
- 'mf-slide-toggle requiere texto visible, `ariaLabel` o `ariaLabelledby` para exponer un nombre accesible.',
120
- );
121
- }
122
- });
123
- }
124
-
125
- readonly controlId = computed(() => this.id() ?? this.generatedId);
126
- readonly hintId = computed(() =>
127
- this.hint() ? `${this.controlId()}-hint` : null,
128
- );
129
- readonly errorId = computed(() =>
130
- this.error() ? `${this.controlId()}-error` : null,
131
- );
132
- readonly describedBy = computed(() =>
133
- mergeAriaIds(this.ariaDescribedby(), this.hintId(), this.errorId()),
134
- );
135
- readonly resolvedAriaLabel = computed(() =>
136
- this.label() ? null : this.ariaLabel() ?? null,
137
- );
138
- readonly isDisabled = computed(
139
- () => this.disabled() || this.disabledFromForm(),
140
- );
141
-
142
- readonly hostClasses = computed(() => {
143
- const classes = ['mf-slide-toggle'];
144
- if (this.isDisabled()) classes.push('mf-slide-toggle--disabled');
145
- if (this.error()) classes.push('mf-slide-toggle--error');
146
- return classes.join(' ');
147
- });
148
-
149
- writeValue(value: boolean | null): void {
150
- this.internalValue.set(Boolean(value));
151
- this.cdr.markForCheck();
152
- }
153
-
154
- registerOnChange(fn: (value: boolean) => void): void {
155
- this.onControlChange = fn;
156
- }
157
-
158
- registerOnTouched(fn: () => void): void {
159
- this.onControlTouched = fn;
160
- }
161
-
162
- setDisabledState(isDisabled: boolean): void {
163
- this.disabledFromForm.set(isDisabled);
164
- this.cdr.markForCheck();
165
- }
166
-
167
- isInvalid(): boolean {
168
- const control = this.ngControl?.control;
169
- return Boolean(
170
- this.error() || (control?.invalid && (control.touched || control.dirty)),
171
- );
172
- }
173
-
174
- onToggleChange(event: { checked: boolean }): void {
175
- this.internalValue.set(event.checked);
176
- this.onControlChange(event.checked);
177
- this.onControlTouched();
178
- this.mfChange.emit(event.checked);
179
- }
180
-
181
- onChange(event: { checked: boolean }): void {
182
- this.onToggleChange(event);
183
- }
184
-
185
- onBlur(): void {
186
- this.onControlTouched();
187
- }
188
- }
@@ -1,2 +0,0 @@
1
- export { MfSnackbarService } from './mf-snackbar.service';
2
- export type { MfSnackbarType, MfSnackbarConfig } from './mf-snackbar.service';
@@ -1,31 +0,0 @@
1
- /* These styles should be placed in a global stylesheet since snackbar renders in overlay */
2
- .mf-snackbar.mat-mdc-snack-bar-container .mdc-snackbar__surface {
3
- border-radius: var(--mf-radius-md) !important;
4
- font-family: var(--mf-font-base) !important;
5
- }
6
-
7
- .mf-snackbar--info.mat-mdc-snack-bar-container .mdc-snackbar__surface {
8
- background-color: var(--mf-color-secondary-700) !important;
9
- color: var(--mf-color-neutral-0) !important;
10
- }
11
-
12
- .mf-snackbar--success.mat-mdc-snack-bar-container .mdc-snackbar__surface {
13
- background-color: var(--mf-color-primary-700) !important;
14
- color: var(--mf-color-on-brand) !important;
15
- }
16
-
17
- .mf-snackbar--warning.mat-mdc-snack-bar-container .mdc-snackbar__surface {
18
- background-color: var(--mf-color-accent-500) !important;
19
- color: var(--mf-color-neutral-900) !important;
20
- }
21
-
22
- .mf-snackbar--error.mat-mdc-snack-bar-container .mdc-snackbar__surface {
23
- background-color: var(--mf-color-error-500) !important;
24
- color: var(--mf-color-neutral-0) !important;
25
- }
26
-
27
- .mf-snackbar .mat-mdc-snack-bar-action .mdc-button__label {
28
- color: inherit !important;
29
- font-weight: var(--mf-weight-bold) !important;
30
- opacity: 0.9;
31
- }
@@ -1,81 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
- import { MatSnackBar } from '@angular/material/snack-bar';
3
- import { MfSnackbarService } from './mf-snackbar.service';
4
- import { NoopAnimationsModule } from '@angular/platform-browser/animations';
5
-
6
- describe('MfSnackbarService', () => {
7
- let service: MfSnackbarService;
8
- let snackBarSpy: { open: ReturnType<typeof vi.fn> };
9
-
10
- beforeEach(() => {
11
- snackBarSpy = { open: vi.fn().mockReturnValue({}) };
12
-
13
- TestBed.configureTestingModule({
14
- imports: [NoopAnimationsModule],
15
- providers: [
16
- MfSnackbarService,
17
- { provide: MatSnackBar, useValue: snackBarSpy },
18
- ],
19
- });
20
-
21
- service = TestBed.inject(MfSnackbarService);
22
- });
23
-
24
- it('should be created', () => {
25
- expect(service).toBeTruthy();
26
- });
27
-
28
- it('should call matSnackBar.open with info type', () => {
29
- service.info('Hello');
30
- expect(snackBarSpy.open).toHaveBeenCalledWith(
31
- 'Hello',
32
- undefined,
33
- expect.objectContaining({
34
- panelClass: ['mf-snackbar', 'mf-snackbar--info'],
35
- }),
36
- );
37
- });
38
-
39
- it('should call matSnackBar.open with success type', () => {
40
- service.success('Done', 'OK');
41
- expect(snackBarSpy.open).toHaveBeenCalledWith(
42
- 'Done',
43
- 'OK',
44
- expect.objectContaining({
45
- panelClass: ['mf-snackbar', 'mf-snackbar--success'],
46
- }),
47
- );
48
- });
49
-
50
- it('should call matSnackBar.open with error type', () => {
51
- service.error('Oops');
52
- expect(snackBarSpy.open).toHaveBeenCalledWith(
53
- 'Oops',
54
- undefined,
55
- expect.objectContaining({
56
- panelClass: ['mf-snackbar', 'mf-snackbar--error'],
57
- }),
58
- );
59
- });
60
-
61
- it('should use custom config', () => {
62
- service.open({
63
- message: 'Custom',
64
- action: 'Undo',
65
- type: 'warning',
66
- duration: 8000,
67
- horizontalPosition: 'center',
68
- verticalPosition: 'top',
69
- });
70
- expect(snackBarSpy.open).toHaveBeenCalledWith(
71
- 'Custom',
72
- 'Undo',
73
- expect.objectContaining({
74
- duration: 8000,
75
- horizontalPosition: 'center',
76
- verticalPosition: 'top',
77
- panelClass: ['mf-snackbar', 'mf-snackbar--warning'],
78
- }),
79
- );
80
- });
81
- });
@@ -1,77 +0,0 @@
1
- import { inject, Injectable } from '@angular/core';
2
- import {
3
- MatSnackBar,
4
- MatSnackBarConfig,
5
- MatSnackBarRef,
6
- TextOnlySnackBar,
7
- } from '@angular/material/snack-bar';
8
-
9
- export type MfSnackbarType = 'info' | 'success' | 'warning' | 'error';
10
- export type MfSnackbarPoliteness = 'off' | 'assertive' | 'polite';
11
-
12
- export interface MfSnackbarConfig {
13
- message: string;
14
- action?: string;
15
- type?: MfSnackbarType;
16
- duration?: number;
17
- horizontalPosition?: 'start' | 'center' | 'end';
18
- verticalPosition?: 'top' | 'bottom';
19
- politeness?: MfSnackbarPoliteness;
20
- announcementMessage?: string;
21
- }
22
-
23
- /**
24
- * Servicio de Snackbar de la librería ng-comps.
25
- * Envuelve Angular Material `MatSnackBar` y expone una API uniforme
26
- * con estilos de marca y tipos semánticos.
27
- */
28
- @Injectable({ providedIn: 'root' })
29
- export class MfSnackbarService {
30
- private readonly matSnackBar = inject(MatSnackBar);
31
-
32
- open(config: MfSnackbarConfig): MatSnackBarRef<TextOnlySnackBar> {
33
- const type = config.type ?? 'info';
34
- const matConfig: MatSnackBarConfig = {
35
- duration: config.duration ?? this.defaultDuration(type),
36
- horizontalPosition: config.horizontalPosition ?? 'end',
37
- verticalPosition: config.verticalPosition ?? 'bottom',
38
- politeness: config.politeness ?? this.defaultPoliteness(type),
39
- announcementMessage: config.announcementMessage ?? config.message,
40
- panelClass: ['mf-snackbar', `mf-snackbar--${type}`],
41
- };
42
-
43
- return this.matSnackBar.open(config.message, config.action, matConfig);
44
- }
45
-
46
- info(message: string, action?: string): MatSnackBarRef<TextOnlySnackBar> {
47
- return this.open({ message, action, type: 'info' });
48
- }
49
-
50
- success(message: string, action?: string): MatSnackBarRef<TextOnlySnackBar> {
51
- return this.open({ message, action, type: 'success' });
52
- }
53
-
54
- warning(message: string, action?: string): MatSnackBarRef<TextOnlySnackBar> {
55
- return this.open({ message, action, type: 'warning' });
56
- }
57
-
58
- error(message: string, action?: string): MatSnackBarRef<TextOnlySnackBar> {
59
- return this.open({ message, action, type: 'error' });
60
- }
61
-
62
- private defaultDuration(type: MfSnackbarType): number {
63
- if (type === 'error' || type === 'warning') {
64
- return 6000;
65
- }
66
-
67
- return 4000;
68
- }
69
-
70
- private defaultPoliteness(type: MfSnackbarType): MfSnackbarPoliteness {
71
- if (type === 'error' || type === 'warning') {
72
- return 'assertive';
73
- }
74
-
75
- return 'polite';
76
- }
77
- }
@@ -1,2 +0,0 @@
1
- export { MfTableComponent } from './mf-table.component';
2
- export type { MfTableColumn, MfTableVariant } from './mf-table.component';