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.
- package/fesm2022/ng-comps.mjs +4254 -0
- package/package.json +54 -58
- package/src/styles.css +54 -0
- package/types/ng-comps.d.ts +1348 -0
- package/.editorconfig +0 -17
- package/.github/copilot-instructions.md +0 -55
- package/.github/workflows/ci.yml +0 -29
- package/.prettierrc +0 -12
- package/.storybook/main.ts +0 -21
- package/.storybook/preview.ts +0 -27
- package/.storybook/tsconfig.doc.json +0 -10
- package/.storybook/tsconfig.json +0 -15
- package/.storybook/typings.d.ts +0 -4
- package/.vscode/extensions.json +0 -4
- package/.vscode/launch.json +0 -20
- package/.vscode/mcp.json +0 -9
- package/.vscode/tasks.json +0 -42
- package/ACCESSIBILITY.md +0 -127
- package/angular.json +0 -106
- package/documentation.json +0 -13394
- package/ng-package.json +0 -27
- package/public/favicon.ico +0 -0
- package/scripts/prepare-package.mjs +0 -80
- package/src/app/a11y/accessibility.utils.ts +0 -35
- package/src/app/a11y/index.ts +0 -6
- package/src/app/accessibility/ng-comps.a11y.spec.ts +0 -108
- package/src/app/app.config.ts +0 -11
- package/src/app/app.css +0 -107
- package/src/app/app.html +0 -48
- package/src/app/app.routes.ts +0 -3
- package/src/app/app.spec.ts +0 -23
- package/src/app/app.ts +0 -10
- package/src/app/components/accordion/index.ts +0 -2
- package/src/app/components/accordion/mf-accordion.component.css +0 -38
- package/src/app/components/accordion/mf-accordion.component.spec.ts +0 -48
- package/src/app/components/accordion/mf-accordion.component.ts +0 -53
- package/src/app/components/alert/index.ts +0 -2
- package/src/app/components/alert/mf-alert.component.css +0 -100
- package/src/app/components/alert/mf-alert.component.spec.ts +0 -59
- package/src/app/components/alert/mf-alert.component.ts +0 -68
- package/src/app/components/autocomplete/index.ts +0 -5
- package/src/app/components/autocomplete/mf-autocomplete.component.css +0 -105
- package/src/app/components/autocomplete/mf-autocomplete.component.spec.ts +0 -116
- package/src/app/components/autocomplete/mf-autocomplete.component.ts +0 -307
- package/src/app/components/avatar/index.ts +0 -2
- package/src/app/components/avatar/mf-avatar.component.css +0 -27
- package/src/app/components/avatar/mf-avatar.component.spec.ts +0 -49
- package/src/app/components/avatar/mf-avatar.component.ts +0 -99
- package/src/app/components/badge/index.ts +0 -2
- package/src/app/components/badge/mf-badge.component.css +0 -32
- package/src/app/components/badge/mf-badge.component.spec.ts +0 -40
- package/src/app/components/badge/mf-badge.component.ts +0 -105
- package/src/app/components/breadcrumb/index.ts +0 -2
- package/src/app/components/breadcrumb/mf-breadcrumb.component.css +0 -61
- package/src/app/components/breadcrumb/mf-breadcrumb.component.spec.ts +0 -61
- package/src/app/components/breadcrumb/mf-breadcrumb.component.ts +0 -75
- package/src/app/components/button/index.ts +0 -2
- package/src/app/components/button/mf-button.component.css +0 -136
- package/src/app/components/button/mf-button.component.ts +0 -174
- package/src/app/components/card/index.ts +0 -2
- package/src/app/components/card/mf-card.component.css +0 -82
- package/src/app/components/card/mf-card.component.ts +0 -59
- package/src/app/components/checkbox/index.ts +0 -1
- package/src/app/components/checkbox/mf-checkbox.component.css +0 -75
- package/src/app/components/checkbox/mf-checkbox.component.ts +0 -187
- package/src/app/components/chip/index.ts +0 -2
- package/src/app/components/chip/mf-chip.component.css +0 -69
- package/src/app/components/chip/mf-chip.component.spec.ts +0 -47
- package/src/app/components/chip/mf-chip.component.ts +0 -77
- package/src/app/components/datepicker/index.ts +0 -2
- package/src/app/components/datepicker/mf-datepicker.component.css +0 -102
- package/src/app/components/datepicker/mf-datepicker.component.spec.ts +0 -69
- package/src/app/components/datepicker/mf-datepicker.component.ts +0 -233
- package/src/app/components/dialog/index.ts +0 -3
- package/src/app/components/dialog/mf-dialog.component.css +0 -73
- package/src/app/components/dialog/mf-dialog.component.ts +0 -160
- package/src/app/components/dialog/mf-dialog.service.spec.ts +0 -61
- package/src/app/components/dialog/mf-dialog.service.ts +0 -52
- package/src/app/components/divider/index.ts +0 -2
- package/src/app/components/divider/mf-divider.component.css +0 -38
- package/src/app/components/divider/mf-divider.component.spec.ts +0 -40
- package/src/app/components/divider/mf-divider.component.ts +0 -44
- package/src/app/components/form-field/index.ts +0 -1
- package/src/app/components/form-field/mf-form-field.component.css +0 -51
- package/src/app/components/form-field/mf-form-field.component.ts +0 -74
- package/src/app/components/grid-list/index.ts +0 -2
- package/src/app/components/grid-list/mf-grid-list.component.css +0 -47
- package/src/app/components/grid-list/mf-grid-list.component.spec.ts +0 -57
- package/src/app/components/grid-list/mf-grid-list.component.ts +0 -68
- package/src/app/components/icon/index.ts +0 -2
- package/src/app/components/icon/mf-icon.component.css +0 -56
- package/src/app/components/icon/mf-icon.component.ts +0 -41
- package/src/app/components/input/index.ts +0 -2
- package/src/app/components/input/mf-input.component.css +0 -105
- package/src/app/components/input/mf-input.component.ts +0 -217
- package/src/app/components/menu/index.ts +0 -2
- package/src/app/components/menu/mf-menu.component.css +0 -31
- package/src/app/components/menu/mf-menu.component.spec.ts +0 -49
- package/src/app/components/menu/mf-menu.component.ts +0 -66
- package/src/app/components/paginator/index.ts +0 -1
- package/src/app/components/paginator/mf-paginator.component.css +0 -32
- package/src/app/components/paginator/mf-paginator.component.spec.ts +0 -44
- package/src/app/components/paginator/mf-paginator.component.ts +0 -52
- package/src/app/components/progress-bar/index.ts +0 -2
- package/src/app/components/progress-bar/mf-progress-bar.component.css +0 -53
- package/src/app/components/progress-bar/mf-progress-bar.component.spec.ts +0 -65
- package/src/app/components/progress-bar/mf-progress-bar.component.ts +0 -79
- package/src/app/components/progress-spinner/index.ts +0 -2
- package/src/app/components/progress-spinner/mf-progress-spinner.component.css +0 -38
- package/src/app/components/progress-spinner/mf-progress-spinner.component.spec.ts +0 -59
- package/src/app/components/progress-spinner/mf-progress-spinner.component.ts +0 -81
- package/src/app/components/radio-button/index.ts +0 -2
- package/src/app/components/radio-button/mf-radio-button.component.css +0 -86
- package/src/app/components/radio-button/mf-radio-button.component.spec.ts +0 -55
- package/src/app/components/radio-button/mf-radio-button.component.ts +0 -219
- package/src/app/components/select/index.ts +0 -2
- package/src/app/components/select/mf-select.component.css +0 -121
- package/src/app/components/select/mf-select.component.spec.ts +0 -108
- package/src/app/components/select/mf-select.component.ts +0 -252
- package/src/app/components/sidenav/index.ts +0 -2
- package/src/app/components/sidenav/mf-sidenav.component.css +0 -168
- package/src/app/components/sidenav/mf-sidenav.component.spec.ts +0 -57
- package/src/app/components/sidenav/mf-sidenav.component.ts +0 -126
- package/src/app/components/slide-toggle/index.ts +0 -1
- package/src/app/components/slide-toggle/mf-slide-toggle.component.css +0 -42
- package/src/app/components/slide-toggle/mf-slide-toggle.component.spec.ts +0 -43
- package/src/app/components/slide-toggle/mf-slide-toggle.component.ts +0 -188
- package/src/app/components/snackbar/index.ts +0 -2
- package/src/app/components/snackbar/mf-snackbar.service.css +0 -31
- package/src/app/components/snackbar/mf-snackbar.service.spec.ts +0 -81
- package/src/app/components/snackbar/mf-snackbar.service.ts +0 -77
- package/src/app/components/table/index.ts +0 -2
- package/src/app/components/table/mf-table.component.css +0 -68
- package/src/app/components/table/mf-table.component.spec.ts +0 -76
- package/src/app/components/table/mf-table.component.ts +0 -117
- package/src/app/components/tabs/index.ts +0 -2
- package/src/app/components/tabs/mf-tabs.component.css +0 -31
- package/src/app/components/tabs/mf-tabs.component.spec.ts +0 -50
- package/src/app/components/tabs/mf-tabs.component.ts +0 -62
- package/src/app/components/textarea/index.ts +0 -2
- package/src/app/components/textarea/mf-textarea.component.css +0 -48
- package/src/app/components/textarea/mf-textarea.component.spec.ts +0 -55
- package/src/app/components/textarea/mf-textarea.component.ts +0 -227
- package/src/app/components/toolbar/index.ts +0 -2
- package/src/app/components/toolbar/mf-toolbar.component.css +0 -77
- package/src/app/components/toolbar/mf-toolbar.component.ts +0 -56
- package/src/app/components/tooltip/index.ts +0 -3
- package/src/app/components/tooltip/mf-tooltip.component.css +0 -7
- package/src/app/components/tooltip/mf-tooltip.component.spec.ts +0 -37
- package/src/app/components/tooltip/mf-tooltip.component.ts +0 -47
- package/src/app/components/tooltip/mf-tooltip.directive.ts +0 -22
- package/src/index.html +0 -18
- package/src/main.ts +0 -6
- package/src/public-api.ts +0 -31
- package/src/stories/About.mdx +0 -72
- package/src/stories/Accessibility.mdx +0 -59
- package/src/stories/Welcome.mdx +0 -26
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +0 -1
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +0 -1
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +0 -1
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +0 -1
- package/src/stories/assets/youtube.svg +0 -1
- package/src/stories/mf-a11y-contracts.stories.ts +0 -472
- package/src/stories/mf-autocomplete.stories.ts +0 -194
- package/src/stories/mf-button.stories.ts +0 -152
- package/src/stories/mf-card.stories.ts +0 -147
- package/src/stories/mf-checkbox.stories.ts +0 -88
- package/src/stories/mf-datepicker.stories.ts +0 -118
- package/src/stories/mf-dialog.stories.ts +0 -159
- package/src/stories/mf-form-field.stories.ts +0 -108
- package/src/stories/mf-grid-list.stories.ts +0 -104
- package/src/stories/mf-icon.stories.ts +0 -133
- package/src/stories/mf-input.stories.ts +0 -158
- package/src/stories/mf-menu.stories.ts +0 -71
- package/src/stories/mf-progress-bar.stories.ts +0 -119
- package/src/stories/mf-progress-spinner.stories.ts +0 -124
- package/src/stories/mf-radio-button.stories.ts +0 -111
- package/src/stories/mf-select.stories.ts +0 -184
- package/src/stories/mf-sidenav.stories.ts +0 -331
- package/src/stories/mf-table.stories.ts +0 -80
- package/src/stories/mf-toolbar.stories.ts +0 -112
- package/src/stories/user.ts +0 -3
- package/tsconfig.app.json +0 -15
- package/tsconfig.json +0 -33
- package/tsconfig.spec.json +0 -15
- 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,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
|
-
}
|