ng-comps 1.0.2 → 3.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,307 +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 { ErrorStateMatcher } from '@angular/material/core';
19
- import { MatAutocompleteModule } from '@angular/material/autocomplete';
20
- import { MatFormFieldModule } from '@angular/material/form-field';
21
- import { MatIconModule } from '@angular/material/icon';
22
- import { MatInputModule } from '@angular/material/input';
23
- import {
24
- createUniqueId,
25
- hasAccessibleName,
26
- mergeAriaIds,
27
- warnInDev,
28
- } from '../../a11y';
29
-
30
- export interface MfAutocompleteOption {
31
- value: string | number;
32
- label: string;
33
- disabled?: boolean;
34
- }
35
-
36
- export type MfAutocompleteSize = 'sm' | 'md' | 'lg';
37
-
38
- /**
39
- * Autocompletar de la librería ng-comps.
40
- * Envuelve Angular Material `mat-autocomplete` y expone una API uniforme
41
- * con look and feel de marca.
42
- *
43
- * El panel desplegable se estiliza con la clase global `mf-autocomplete-panel`.
44
- * Puedes añadir clases adicionales con `panelClass`.
45
- */
46
- @Component({
47
- selector: 'mf-autocomplete',
48
- imports: [
49
- MatAutocompleteModule,
50
- MatFormFieldModule,
51
- MatIconModule,
52
- MatInputModule,
53
- ],
54
- providers: [
55
- {
56
- provide: NG_VALUE_ACCESSOR,
57
- useExisting: forwardRef(() => MfAutocompleteComponent),
58
- multi: true,
59
- },
60
- ],
61
- template: `
62
- <mat-form-field [appearance]="'outline'" [class]="hostClasses()">
63
- @if (label()) {
64
- <mat-label>{{ label() }}</mat-label>
65
- }
66
- @if (leadingIcon()) {
67
- <mat-icon matPrefix aria-hidden="true">{{ leadingIcon() }}</mat-icon>
68
- }
69
- <input
70
- matInput
71
- [id]="controlId()"
72
- [placeholder]="placeholder()"
73
- [disabled]="isDisabled()"
74
- [required]="required()"
75
- [matAutocomplete]="auto"
76
- [value]="internalValue()"
77
- [errorStateMatcher]="errorStateMatcher"
78
- [attr.aria-label]="resolvedAriaLabel()"
79
- [attr.aria-labelledby]="ariaLabelledby() || null"
80
- [attr.aria-describedby]="describedBy()"
81
- [attr.aria-invalid]="isInvalid() ? 'true' : null"
82
- [attr.aria-required]="required() ? 'true' : null"
83
- (input)="onInputChange($event)"
84
- (blur)="onBlur()"
85
- />
86
- @if (trailingIcon()) {
87
- <mat-icon matSuffix aria-hidden="true">{{ trailingIcon() }}</mat-icon>
88
- }
89
- @if (hint()) {
90
- <mat-hint [attr.id]="hintId()">{{ hint() }}</mat-hint>
91
- }
92
- <mat-autocomplete
93
- #auto
94
- [panelWidth]="panelWidth()"
95
- [class]="autocompletePanelClasses()"
96
- (optionSelected)="onOptionSelected($event)"
97
- >
98
- @for (option of filteredOptions(); track option.value) {
99
- <mat-option [value]="option.label" [disabled]="option.disabled ?? false">
100
- {{ option.label }}
101
- </mat-option>
102
- }
103
- </mat-autocomplete>
104
- </mat-form-field>
105
- @if (error()) {
106
- <p class="mf-autocomplete__error" [attr.id]="errorId()" role="alert">
107
- {{ error() }}
108
- </p>
109
- }
110
- `,
111
- styleUrl: './mf-autocomplete.component.css',
112
- changeDetection: ChangeDetectionStrategy.OnPush,
113
- })
114
- export class MfAutocompleteComponent implements ControlValueAccessor {
115
- private readonly cdr = inject(ChangeDetectorRef);
116
- private readonly ngControl = inject(NgControl, { self: true, optional: true });
117
- private readonly generatedId = createUniqueId('mf-autocomplete');
118
- private readonly disabledFromForm = signal(false);
119
- protected readonly internalValue = signal('');
120
- private onControlChange: (value: string) => void = () => undefined;
121
- private onControlTouched: () => void = () => undefined;
122
- readonly errorStateMatcher: ErrorStateMatcher = {
123
- isErrorState: (control) =>
124
- Boolean(
125
- this.error() || (control?.invalid && (control.touched || control.dirty)),
126
- ),
127
- };
128
-
129
- /** Lista completa de opciones */
130
- readonly options = input.required<MfAutocompleteOption[]>();
131
- /** ID del control */
132
- readonly id = input<string | undefined>(undefined);
133
- /** Etiqueta flotante del campo */
134
- readonly label = input<string | undefined>(undefined);
135
- /** Etiqueta accesible alternativa cuando no existe label visible */
136
- readonly ariaLabel = input<string | undefined>(undefined);
137
- /** Referencia externa a elementos que etiquetan el control */
138
- readonly ariaLabelledby = input<string | undefined>(undefined);
139
- /** Referencia externa a elementos descriptivos adicionales */
140
- readonly ariaDescribedby = input<string | undefined>(undefined);
141
- /** Placeholder del input */
142
- readonly placeholder = input('');
143
- /** Valor actual (texto en el campo) */
144
- readonly value = input('');
145
- /** Deshabilitado */
146
- readonly disabled = input(false);
147
- /** Requerido */
148
- readonly required = input(false);
149
- /** Tamaño del campo */
150
- readonly size = input<MfAutocompleteSize>('md');
151
- /** Ancho completo */
152
- readonly fullWidth = input(false);
153
- /** Texto de ayuda debajo del campo */
154
- readonly hint = input<string | undefined>(undefined);
155
- /** Mensaje de error */
156
- readonly error = input<string | undefined>(undefined);
157
- /** Icono al inicio del campo */
158
- readonly leadingIcon = input<string | undefined>(undefined);
159
- /** Icono al final del campo */
160
- readonly trailingIcon = input<string | undefined>(undefined);
161
- /**
162
- * Ancho del panel del autocomplete.
163
- * Por defecto `''`: el panel toma el mismo ancho que el campo.
164
- * Acepta cualquier valor CSS válido ('300px', '80vw', etc.) para sobreescribirlo.
165
- */
166
- readonly panelWidth = input<string | number>('');
167
- /**
168
- * Clases extra que se añaden al panel del autocomplete (overlay).
169
- * La clase `mf-autocomplete-panel` siempre está presente.
170
- */
171
- readonly panelClass = input<string | string[]>('');
172
-
173
- /** Emite el texto escrito en el campo */
174
- readonly mfInput = output<string>();
175
- /** Emite el valor de la opción seleccionada */
176
- readonly mfOptionSelected = output<MfAutocompleteOption>();
177
- readonly mfBlur = output<void>();
178
-
179
- constructor() {
180
- effect(() => {
181
- this.internalValue.set(this.value());
182
- });
183
-
184
- effect(() => {
185
- if (
186
- !hasAccessibleName(
187
- this.label(),
188
- this.ariaLabel(),
189
- this.ariaLabelledby(),
190
- )
191
- ) {
192
- warnInDev(
193
- 'mf-autocomplete requiere `label`, `ariaLabel` o `ariaLabelledby` para exponer un nombre accesible.',
194
- );
195
- }
196
- });
197
- }
198
-
199
- readonly controlId = computed(() => this.id() ?? this.generatedId);
200
- readonly hintId = computed(() =>
201
- this.hint() ? `${this.controlId()}-hint` : null,
202
- );
203
- readonly errorId = computed(() =>
204
- this.error() ? `${this.controlId()}-error` : null,
205
- );
206
- readonly describedBy = computed(() =>
207
- mergeAriaIds(this.ariaDescribedby(), this.hintId(), this.errorId()),
208
- );
209
- readonly resolvedAriaLabel = computed(() =>
210
- this.label() ? null : this.ariaLabel() ?? null,
211
- );
212
- readonly isDisabled = computed(
213
- () => this.disabled() || this.disabledFromForm(),
214
- );
215
-
216
- readonly hostClasses = computed(() => {
217
- const classes = ['mf-autocomplete', `mf-autocomplete--${this.size()}`];
218
- if (this.fullWidth()) classes.push('mf-autocomplete--full');
219
- if (this.error()) classes.push('mf-autocomplete--error');
220
- return classes.join(' ');
221
- });
222
-
223
- readonly autocompletePanelClasses = computed(() => {
224
- const base = ['mf-autocomplete-panel'];
225
- const extra = this.panelClass();
226
-
227
- if (Array.isArray(extra)) {
228
- base.push(...extra.filter(Boolean));
229
- } else if (extra) {
230
- base.push(extra);
231
- }
232
-
233
- return base.join(' ');
234
- });
235
-
236
- readonly filteredOptions = computed(() => {
237
- const query = this.internalValue().toLowerCase().trim();
238
- if (!query) {
239
- return this.options();
240
- }
241
-
242
- return this.options().filter((option) =>
243
- option.label.toLowerCase().includes(query),
244
- );
245
- });
246
-
247
- get inputValue(): string {
248
- return this.internalValue();
249
- }
250
-
251
- set inputValue(value: string) {
252
- this.internalValue.set(value);
253
- }
254
-
255
- writeValue(value: string | null): void {
256
- this.internalValue.set(value ?? '');
257
- this.cdr.markForCheck();
258
- }
259
-
260
- registerOnChange(fn: (value: string) => void): void {
261
- this.onControlChange = fn;
262
- }
263
-
264
- registerOnTouched(fn: () => void): void {
265
- this.onControlTouched = fn;
266
- }
267
-
268
- setDisabledState(isDisabled: boolean): void {
269
- this.disabledFromForm.set(isDisabled);
270
- this.cdr.markForCheck();
271
- }
272
-
273
- isInvalid(): boolean {
274
- const control = this.ngControl?.control;
275
- return Boolean(
276
- this.error() || (control?.invalid && (control.touched || control.dirty)),
277
- );
278
- }
279
-
280
- onInputChange(event: Event | string): void {
281
- const value =
282
- typeof event === 'string'
283
- ? event
284
- : (event.target as HTMLInputElement).value;
285
-
286
- this.internalValue.set(value);
287
- this.onControlChange(value);
288
- this.mfInput.emit(value);
289
- }
290
-
291
- onOptionSelected(event: { option: { value: string } }): void {
292
- const label = event.option.value;
293
- const match = this.options().find((option) => option.label === label);
294
-
295
- if (match) {
296
- this.internalValue.set(match.label);
297
- this.onControlChange(match.label);
298
- this.onControlTouched();
299
- this.mfOptionSelected.emit(match);
300
- }
301
- }
302
-
303
- onBlur(): void {
304
- this.onControlTouched();
305
- this.mfBlur.emit();
306
- }
307
- }
@@ -1,2 +0,0 @@
1
- export { MfAvatarComponent } from './mf-avatar.component';
2
- export type { MfAvatarSize, MfAvatarVariant } from './mf-avatar.component';
@@ -1,27 +0,0 @@
1
- :host {
2
- display: inline-block;
3
- }
4
-
5
- .mf-avatar {
6
- display: inline-flex;
7
- align-items: center;
8
- justify-content: center;
9
- overflow: hidden;
10
- object-fit: cover;
11
- background-color: var(--mf-color-primary-100);
12
- color: var(--mf-color-primary-800);
13
- font-family: var(--mf-font-base);
14
- font-weight: var(--mf-weight-bold);
15
- user-select: none;
16
- }
17
-
18
- /* ── Tamaños ───────────────────────────────────────────────────── */
19
- .mf-avatar--xs { width: 24px; height: 24px; font-size: var(--mf-text-xs); }
20
- .mf-avatar--sm { width: 32px; height: 32px; font-size: var(--mf-text-sm); }
21
- .mf-avatar--md { width: 40px; height: 40px; font-size: var(--mf-text-base); }
22
- .mf-avatar--lg { width: 56px; height: 56px; font-size: var(--mf-text-xl); }
23
- .mf-avatar--xl { width: 80px; height: 80px; font-size: var(--mf-text-3xl); }
24
-
25
- /* ── Forma ─────────────────────────────────────────────────────── */
26
- .mf-avatar--circle { border-radius: var(--mf-radius-full); }
27
- .mf-avatar--rounded { border-radius: var(--mf-radius-md); }
@@ -1,49 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { MfAvatarComponent } from './mf-avatar.component';
3
-
4
- describe('MfAvatarComponent', () => {
5
- let fixture: ComponentFixture<MfAvatarComponent>;
6
- let component: MfAvatarComponent;
7
-
8
- beforeEach(async () => {
9
- await TestBed.configureTestingModule({
10
- imports: [MfAvatarComponent],
11
- }).compileComponents();
12
-
13
- fixture = TestBed.createComponent(MfAvatarComponent);
14
- component = fixture.componentInstance;
15
- fixture.detectChanges();
16
- });
17
-
18
- it('should create', () => {
19
- expect(component).toBeTruthy();
20
- });
21
-
22
- it('should show initials when no src', () => {
23
- fixture.componentRef.setInput('name', 'John Doe');
24
- fixture.detectChanges();
25
- const span = fixture.nativeElement.querySelector('.mf-avatar');
26
- expect(span?.textContent?.trim()).toBe('JD');
27
- });
28
-
29
- it('should show ? when no name and no src', () => {
30
- fixture.detectChanges();
31
- const span = fixture.nativeElement.querySelector('.mf-avatar');
32
- expect(span?.textContent?.trim()).toBe('?');
33
- });
34
-
35
- it('should render img when src provided', () => {
36
- fixture.componentRef.setInput('src', 'https://example.com/photo.jpg');
37
- fixture.detectChanges();
38
- const img = fixture.nativeElement.querySelector('img');
39
- expect(img).toBeTruthy();
40
- });
41
-
42
- it('should apply circle variant by default', () => {
43
- expect(component.hostClasses()).toContain('mf-avatar--circle');
44
- });
45
-
46
- it('should apply md size by default', () => {
47
- expect(component.hostClasses()).toContain('mf-avatar--md');
48
- });
49
- });
@@ -1,99 +0,0 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- Component,
4
- computed,
5
- effect,
6
- input,
7
- } from '@angular/core';
8
- import { hasAccessibleName, warnInDev } from '../../a11y';
9
-
10
- export type MfAvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
11
- export type MfAvatarVariant = 'circle' | 'rounded';
12
-
13
- /**
14
- * Avatar de la librería ng-comps.
15
- * Muestra una imagen de perfil, iniciales o un icono.
16
- * Componente puro sin dependencia de Angular Material.
17
- */
18
- @Component({
19
- selector: 'mf-avatar',
20
- imports: [],
21
- template: `
22
- @if (src()) {
23
- <img
24
- [src]="src()"
25
- [alt]="imageAlt()"
26
- [class]="hostClasses()"
27
- [attr.aria-hidden]="decorative() ? 'true' : null"
28
- />
29
- } @else {
30
- <span
31
- [class]="hostClasses()"
32
- [attr.aria-label]="fallbackAriaLabel()"
33
- [attr.aria-hidden]="decorative() ? 'true' : null"
34
- >
35
- {{ initials() }}
36
- </span>
37
- }
38
- `,
39
- styleUrl: './mf-avatar.component.css',
40
- changeDetection: ChangeDetectionStrategy.OnPush,
41
- })
42
- export class MfAvatarComponent {
43
- /** URL de la imagen */
44
- readonly src = input<string | undefined>(undefined);
45
- /** Texto alternativo */
46
- readonly alt = input<string>('');
47
- /** Nombre completo para generar iniciales */
48
- readonly name = input<string>('');
49
- /** Si es decorativo, se oculta a tecnologías asistivas */
50
- readonly decorative = input(false);
51
- /** Tamaño */
52
- readonly size = input<MfAvatarSize>('md');
53
- /** Forma */
54
- readonly variant = input<MfAvatarVariant>('circle');
55
-
56
- constructor() {
57
- effect(() => {
58
- if (!this.decorative() && !hasAccessibleName(this.alt(), this.name())) {
59
- warnInDev(
60
- 'mf-avatar requiere `alt` o `name` cuando no es decorativo para exponer un nombre accesible.',
61
- );
62
- }
63
- });
64
- }
65
-
66
- readonly initials = computed(() => {
67
- const n = this.name();
68
- if (!n) return '?';
69
- const parts = n.trim().split(/\s+/);
70
- if (parts.length === 1) return parts[0].charAt(0).toUpperCase();
71
- return (
72
- parts[0].charAt(0) + parts[parts.length - 1].charAt(0)
73
- ).toUpperCase();
74
- });
75
-
76
- readonly imageAlt = computed(() => {
77
- if (this.decorative()) {
78
- return '';
79
- }
80
-
81
- return this.alt() || this.name();
82
- });
83
-
84
- readonly fallbackAriaLabel = computed(() => {
85
- if (this.decorative()) {
86
- return null;
87
- }
88
-
89
- return this.alt() || this.name() || null;
90
- });
91
-
92
- readonly hostClasses = computed(() => {
93
- return [
94
- 'mf-avatar',
95
- `mf-avatar--${this.size()}`,
96
- `mf-avatar--${this.variant()}`,
97
- ].join(' ');
98
- });
99
- }
@@ -1,2 +0,0 @@
1
- export { MfBadgeComponent } from './mf-badge.component';
2
- export type { MfBadgeColor, MfBadgeSize, MfBadgePosition } from './mf-badge.component';
@@ -1,32 +0,0 @@
1
- :host {
2
- display: inline-block;
3
- }
4
-
5
- /* ── Brand ────────────────────────────────────────────────────── */
6
- .mf-badge--brand .mat-badge-content {
7
- background-color: var(--mf-color-brand) !important;
8
- color: var(--mf-color-on-brand) !important;
9
- }
10
-
11
- /* ── Accent ───────────────────────────────────────────────────── */
12
- .mf-badge--accent .mat-badge-content {
13
- background-color: var(--mf-color-accent-500) !important;
14
- color: var(--mf-color-neutral-900) !important;
15
- }
16
-
17
- /* ── Error ────────────────────────────────────────────────────── */
18
- .mf-badge--error .mat-badge-content {
19
- background-color: var(--mf-color-error-500) !important;
20
- color: var(--mf-color-neutral-0) !important;
21
- }
22
-
23
- /* ── Neutral ──────────────────────────────────────────────────── */
24
- .mf-badge--neutral .mat-badge-content {
25
- background-color: var(--mf-color-neutral-400) !important;
26
- color: var(--mf-color-neutral-0) !important;
27
- }
28
-
29
- .mat-badge-content {
30
- font-family: var(--mf-font-base) !important;
31
- font-weight: var(--mf-weight-bold) !important;
32
- }
@@ -1,40 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { MfBadgeComponent } from './mf-badge.component';
3
-
4
- describe('MfBadgeComponent', () => {
5
- let fixture: ComponentFixture<MfBadgeComponent>;
6
- let component: MfBadgeComponent;
7
-
8
- beforeEach(async () => {
9
- await TestBed.configureTestingModule({
10
- imports: [MfBadgeComponent],
11
- }).compileComponents();
12
-
13
- fixture = TestBed.createComponent(MfBadgeComponent);
14
- component = fixture.componentInstance;
15
- fixture.detectChanges();
16
- });
17
-
18
- it('should create', () => {
19
- expect(component).toBeTruthy();
20
- });
21
-
22
- it('should apply brand class by default', () => {
23
- expect(component.hostClasses()).toContain('mf-badge--brand');
24
- });
25
-
26
- it('should apply error class when color is error', () => {
27
- fixture.componentRef.setInput('color', 'error');
28
- expect(component.hostClasses()).toContain('mf-badge--error');
29
- });
30
-
31
- it('should map size correctly', () => {
32
- fixture.componentRef.setInput('size', 'sm');
33
- expect(component.matSize()).toBe('small');
34
- });
35
-
36
- it('should map position correctly', () => {
37
- fixture.componentRef.setInput('position', 'below-before');
38
- expect(component.matPosition()).toBe('below before');
39
- });
40
- });
@@ -1,105 +0,0 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- Component,
4
- computed,
5
- effect,
6
- input,
7
- } from '@angular/core';
8
- import { MatBadgeModule } from '@angular/material/badge';
9
- import { warnInDev } from '../../a11y';
10
-
11
- export type MfBadgeColor = 'brand' | 'accent' | 'error' | 'neutral';
12
- export type MfBadgeSize = 'sm' | 'md' | 'lg';
13
- export type MfBadgePosition =
14
- | 'above-after'
15
- | 'above-before'
16
- | 'below-after'
17
- | 'below-before';
18
-
19
- /**
20
- * Badge de la librería ng-comps.
21
- * Envuelve Angular Material `matBadge` y expone una API uniforme
22
- * con look and feel de marca.
23
- */
24
- @Component({
25
- selector: 'mf-badge',
26
- imports: [MatBadgeModule],
27
- template: `
28
- <span
29
- [matBadge]="content()"
30
- [matBadgeHidden]="hidden()"
31
- [matBadgeDisabled]="disabled()"
32
- [matBadgeOverlap]="overlap()"
33
- [matBadgePosition]="matPosition()"
34
- [matBadgeSize]="matSize()"
35
- [matBadgeDescription]="badgeDescription()"
36
- [attr.aria-hidden]="decorative() ? 'true' : null"
37
- [class]="hostClasses()"
38
- >
39
- <ng-content />
40
- </span>
41
- `,
42
- styleUrl: './mf-badge.component.css',
43
- changeDetection: ChangeDetectionStrategy.OnPush,
44
- })
45
- export class MfBadgeComponent {
46
- /** Contenido del badge (texto o número) */
47
- readonly content = input<string | number>('');
48
- /** Descripción accesible del badge */
49
- readonly description = input<string | undefined>(undefined);
50
- /** Si es decorativo, se oculta a tecnologías asistivas */
51
- readonly decorative = input(false);
52
- /** Color semántico */
53
- readonly color = input<MfBadgeColor>('brand');
54
- /** Tamaño del badge */
55
- readonly size = input<MfBadgeSize>('md');
56
- /** Posición del badge */
57
- readonly position = input<MfBadgePosition>('above-after');
58
- /** Ocultar el badge */
59
- readonly hidden = input(false);
60
- /** Deshabilitado */
61
- readonly disabled = input(false);
62
- /** Superponer sobre el contenido */
63
- readonly overlap = input(true);
64
-
65
- constructor() {
66
- effect(() => {
67
- if (!this.decorative() && this.content() !== '' && !this.description()) {
68
- warnInDev(
69
- 'mf-badge debería incluir `description` cuando el badge transmite información relevante.',
70
- );
71
- }
72
- });
73
- }
74
-
75
- readonly badgeDescription = computed(() =>
76
- this.decorative() ? '' : this.description() ?? '',
77
- );
78
-
79
- readonly matPosition = computed(() => {
80
- const pos = this.position();
81
- const map: Record<
82
- MfBadgePosition,
83
- 'above after' | 'above before' | 'below after' | 'below before'
84
- > = {
85
- 'above-after': 'above after',
86
- 'above-before': 'above before',
87
- 'below-after': 'below after',
88
- 'below-before': 'below before',
89
- };
90
- return map[pos];
91
- });
92
-
93
- readonly matSize = computed((): 'small' | 'medium' | 'large' => {
94
- const map: Record<MfBadgeSize, 'small' | 'medium' | 'large'> = {
95
- sm: 'small',
96
- md: 'medium',
97
- lg: 'large',
98
- };
99
- return map[this.size()];
100
- });
101
-
102
- readonly hostClasses = computed(() => {
103
- return ['mf-badge', `mf-badge--${this.color()}`].join(' ');
104
- });
105
- }
@@ -1,2 +0,0 @@
1
- export { MfBreadcrumbComponent } from './mf-breadcrumb.component';
2
- export type { MfBreadcrumbItem } from './mf-breadcrumb.component';