ng-comps 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/.editorconfig +17 -0
  2. package/.github/copilot-instructions.md +55 -0
  3. package/.github/workflows/ci.yml +29 -0
  4. package/.prettierrc +12 -0
  5. package/.storybook/main.ts +21 -0
  6. package/.storybook/preview.ts +27 -0
  7. package/.storybook/tsconfig.doc.json +10 -0
  8. package/.storybook/tsconfig.json +15 -0
  9. package/.storybook/typings.d.ts +4 -0
  10. package/.vscode/extensions.json +4 -0
  11. package/.vscode/launch.json +20 -0
  12. package/.vscode/mcp.json +9 -0
  13. package/.vscode/tasks.json +42 -0
  14. package/ACCESSIBILITY.md +127 -0
  15. package/README.md +79 -62
  16. package/angular.json +105 -0
  17. package/documentation.json +13394 -0
  18. package/ng-package.json +27 -0
  19. package/package.json +58 -45
  20. package/public/favicon.ico +0 -0
  21. package/scripts/prepare-package.mjs +61 -0
  22. package/src/app/a11y/accessibility.utils.ts +35 -0
  23. package/src/app/a11y/index.ts +6 -0
  24. package/src/app/accessibility/ng-comps.a11y.spec.ts +108 -0
  25. package/src/app/app.config.ts +11 -0
  26. package/src/app/app.css +107 -0
  27. package/src/app/app.html +48 -0
  28. package/src/app/app.routes.ts +3 -0
  29. package/src/app/app.spec.ts +23 -0
  30. package/src/app/app.ts +10 -0
  31. package/src/app/components/accordion/index.ts +2 -0
  32. package/src/app/components/accordion/mf-accordion.component.css +38 -0
  33. package/src/app/components/accordion/mf-accordion.component.spec.ts +48 -0
  34. package/src/app/components/accordion/mf-accordion.component.ts +53 -0
  35. package/src/app/components/alert/index.ts +2 -0
  36. package/src/app/components/alert/mf-alert.component.css +100 -0
  37. package/src/app/components/alert/mf-alert.component.spec.ts +59 -0
  38. package/src/app/components/alert/mf-alert.component.ts +68 -0
  39. package/src/app/components/autocomplete/index.ts +5 -0
  40. package/src/app/components/autocomplete/mf-autocomplete.component.css +105 -0
  41. package/src/app/components/autocomplete/mf-autocomplete.component.spec.ts +116 -0
  42. package/src/app/components/autocomplete/mf-autocomplete.component.ts +307 -0
  43. package/src/app/components/avatar/index.ts +2 -0
  44. package/src/app/components/avatar/mf-avatar.component.css +27 -0
  45. package/src/app/components/avatar/mf-avatar.component.spec.ts +49 -0
  46. package/src/app/components/avatar/mf-avatar.component.ts +99 -0
  47. package/src/app/components/badge/index.ts +2 -0
  48. package/src/app/components/badge/mf-badge.component.css +32 -0
  49. package/src/app/components/badge/mf-badge.component.spec.ts +40 -0
  50. package/src/app/components/badge/mf-badge.component.ts +105 -0
  51. package/src/app/components/breadcrumb/index.ts +2 -0
  52. package/src/app/components/breadcrumb/mf-breadcrumb.component.css +61 -0
  53. package/src/app/components/breadcrumb/mf-breadcrumb.component.spec.ts +61 -0
  54. package/src/app/components/breadcrumb/mf-breadcrumb.component.ts +75 -0
  55. package/src/app/components/button/index.ts +2 -0
  56. package/src/app/components/button/mf-button.component.css +136 -0
  57. package/src/app/components/button/mf-button.component.ts +174 -0
  58. package/src/app/components/card/index.ts +2 -0
  59. package/src/app/components/card/mf-card.component.css +82 -0
  60. package/src/app/components/card/mf-card.component.ts +59 -0
  61. package/src/app/components/checkbox/index.ts +1 -0
  62. package/src/app/components/checkbox/mf-checkbox.component.css +75 -0
  63. package/src/app/components/checkbox/mf-checkbox.component.ts +187 -0
  64. package/src/app/components/chip/index.ts +2 -0
  65. package/src/app/components/chip/mf-chip.component.css +69 -0
  66. package/src/app/components/chip/mf-chip.component.spec.ts +47 -0
  67. package/src/app/components/chip/mf-chip.component.ts +77 -0
  68. package/src/app/components/datepicker/index.ts +2 -0
  69. package/src/app/components/datepicker/mf-datepicker.component.css +102 -0
  70. package/src/app/components/datepicker/mf-datepicker.component.spec.ts +69 -0
  71. package/src/app/components/datepicker/mf-datepicker.component.ts +233 -0
  72. package/src/app/components/dialog/index.ts +3 -0
  73. package/src/app/components/dialog/mf-dialog.component.css +73 -0
  74. package/src/app/components/dialog/mf-dialog.component.ts +160 -0
  75. package/src/app/components/dialog/mf-dialog.service.spec.ts +61 -0
  76. package/src/app/components/dialog/mf-dialog.service.ts +52 -0
  77. package/src/app/components/divider/index.ts +2 -0
  78. package/src/app/components/divider/mf-divider.component.css +38 -0
  79. package/src/app/components/divider/mf-divider.component.spec.ts +40 -0
  80. package/src/app/components/divider/mf-divider.component.ts +44 -0
  81. package/src/app/components/form-field/index.ts +1 -0
  82. package/src/app/components/form-field/mf-form-field.component.css +51 -0
  83. package/src/app/components/form-field/mf-form-field.component.ts +74 -0
  84. package/src/app/components/grid-list/index.ts +2 -0
  85. package/src/app/components/grid-list/mf-grid-list.component.css +47 -0
  86. package/src/app/components/grid-list/mf-grid-list.component.spec.ts +57 -0
  87. package/src/app/components/grid-list/mf-grid-list.component.ts +68 -0
  88. package/src/app/components/icon/index.ts +2 -0
  89. package/src/app/components/icon/mf-icon.component.css +56 -0
  90. package/src/app/components/icon/mf-icon.component.ts +41 -0
  91. package/src/app/components/input/index.ts +2 -0
  92. package/src/app/components/input/mf-input.component.css +105 -0
  93. package/src/app/components/input/mf-input.component.ts +217 -0
  94. package/src/app/components/menu/index.ts +2 -0
  95. package/src/app/components/menu/mf-menu.component.css +31 -0
  96. package/src/app/components/menu/mf-menu.component.spec.ts +49 -0
  97. package/src/app/components/menu/mf-menu.component.ts +66 -0
  98. package/src/app/components/paginator/index.ts +1 -0
  99. package/src/app/components/paginator/mf-paginator.component.css +32 -0
  100. package/src/app/components/paginator/mf-paginator.component.spec.ts +44 -0
  101. package/src/app/components/paginator/mf-paginator.component.ts +52 -0
  102. package/src/app/components/progress-bar/index.ts +2 -0
  103. package/src/app/components/progress-bar/mf-progress-bar.component.css +53 -0
  104. package/src/app/components/progress-bar/mf-progress-bar.component.spec.ts +65 -0
  105. package/src/app/components/progress-bar/mf-progress-bar.component.ts +79 -0
  106. package/src/app/components/progress-spinner/index.ts +2 -0
  107. package/src/app/components/progress-spinner/mf-progress-spinner.component.css +38 -0
  108. package/src/app/components/progress-spinner/mf-progress-spinner.component.spec.ts +59 -0
  109. package/src/app/components/progress-spinner/mf-progress-spinner.component.ts +81 -0
  110. package/src/app/components/radio-button/index.ts +2 -0
  111. package/src/app/components/radio-button/mf-radio-button.component.css +86 -0
  112. package/src/app/components/radio-button/mf-radio-button.component.spec.ts +55 -0
  113. package/src/app/components/radio-button/mf-radio-button.component.ts +219 -0
  114. package/src/app/components/select/index.ts +2 -0
  115. package/src/app/components/select/mf-select.component.css +121 -0
  116. package/src/app/components/select/mf-select.component.spec.ts +108 -0
  117. package/src/app/components/select/mf-select.component.ts +252 -0
  118. package/src/app/components/sidenav/index.ts +2 -0
  119. package/src/app/components/sidenav/mf-sidenav.component.css +168 -0
  120. package/src/app/components/sidenav/mf-sidenav.component.spec.ts +57 -0
  121. package/src/app/components/sidenav/mf-sidenav.component.ts +126 -0
  122. package/src/app/components/slide-toggle/index.ts +1 -0
  123. package/src/app/components/slide-toggle/mf-slide-toggle.component.css +42 -0
  124. package/src/app/components/slide-toggle/mf-slide-toggle.component.spec.ts +43 -0
  125. package/src/app/components/slide-toggle/mf-slide-toggle.component.ts +188 -0
  126. package/src/app/components/snackbar/index.ts +2 -0
  127. package/src/app/components/snackbar/mf-snackbar.service.css +31 -0
  128. package/src/app/components/snackbar/mf-snackbar.service.spec.ts +81 -0
  129. package/src/app/components/snackbar/mf-snackbar.service.ts +77 -0
  130. package/src/app/components/table/index.ts +2 -0
  131. package/src/app/components/table/mf-table.component.css +68 -0
  132. package/src/app/components/table/mf-table.component.spec.ts +76 -0
  133. package/src/app/components/table/mf-table.component.ts +117 -0
  134. package/src/app/components/tabs/index.ts +2 -0
  135. package/src/app/components/tabs/mf-tabs.component.css +31 -0
  136. package/src/app/components/tabs/mf-tabs.component.spec.ts +50 -0
  137. package/src/app/components/tabs/mf-tabs.component.ts +62 -0
  138. package/src/app/components/textarea/index.ts +2 -0
  139. package/src/app/components/textarea/mf-textarea.component.css +48 -0
  140. package/src/app/components/textarea/mf-textarea.component.spec.ts +55 -0
  141. package/src/app/components/textarea/mf-textarea.component.ts +227 -0
  142. package/src/app/components/toolbar/index.ts +2 -0
  143. package/src/app/components/toolbar/mf-toolbar.component.css +77 -0
  144. package/src/app/components/toolbar/mf-toolbar.component.ts +56 -0
  145. package/src/app/components/tooltip/index.ts +3 -0
  146. package/src/app/components/tooltip/mf-tooltip.component.css +7 -0
  147. package/src/app/components/tooltip/mf-tooltip.component.spec.ts +37 -0
  148. package/src/app/components/tooltip/mf-tooltip.component.ts +47 -0
  149. package/src/app/components/tooltip/mf-tooltip.directive.ts +22 -0
  150. package/src/index.html +18 -0
  151. package/src/main.ts +6 -0
  152. package/src/public-api.ts +31 -0
  153. package/src/stories/About.mdx +72 -0
  154. package/src/stories/Accessibility.mdx +59 -0
  155. package/src/stories/Welcome.mdx +27 -0
  156. package/src/stories/assets/accessibility.png +0 -0
  157. package/src/stories/assets/accessibility.svg +1 -0
  158. package/src/stories/assets/addon-library.png +0 -0
  159. package/src/stories/assets/assets.png +0 -0
  160. package/src/stories/assets/avif-test-image.avif +0 -0
  161. package/src/stories/assets/context.png +0 -0
  162. package/src/stories/assets/discord.svg +1 -0
  163. package/src/stories/assets/docs.png +0 -0
  164. package/src/stories/assets/figma-plugin.png +0 -0
  165. package/src/stories/assets/github.svg +1 -0
  166. package/src/stories/assets/share.png +0 -0
  167. package/src/stories/assets/styling.png +0 -0
  168. package/src/stories/assets/testing.png +0 -0
  169. package/src/stories/assets/theming.png +0 -0
  170. package/src/stories/assets/tutorials.svg +1 -0
  171. package/src/stories/assets/youtube.svg +1 -0
  172. package/src/stories/mf-a11y-contracts.stories.ts +472 -0
  173. package/src/stories/mf-autocomplete.stories.ts +188 -0
  174. package/src/stories/mf-button.stories.ts +156 -0
  175. package/src/stories/mf-card.stories.ts +148 -0
  176. package/src/stories/mf-checkbox.stories.ts +88 -0
  177. package/src/stories/mf-datepicker.stories.ts +118 -0
  178. package/src/stories/mf-dialog.stories.ts +167 -0
  179. package/src/stories/mf-form-field.stories.ts +108 -0
  180. package/src/stories/mf-grid-list.stories.ts +105 -0
  181. package/src/stories/mf-icon.stories.ts +130 -0
  182. package/src/stories/mf-input.stories.ts +158 -0
  183. package/src/stories/mf-menu.stories.ts +71 -0
  184. package/src/stories/mf-progress-bar.stories.ts +119 -0
  185. package/src/stories/mf-progress-spinner.stories.ts +124 -0
  186. package/src/stories/mf-radio-button.stories.ts +111 -0
  187. package/src/stories/mf-select.stories.ts +178 -0
  188. package/src/stories/mf-sidenav.stories.ts +334 -0
  189. package/src/stories/mf-table.stories.ts +80 -0
  190. package/src/stories/mf-toolbar.stories.ts +112 -0
  191. package/src/stories/user.ts +3 -0
  192. package/src/styles.css +58 -21
  193. package/src/theme/tokens.css +7 -4
  194. package/tsconfig.app.json +15 -0
  195. package/tsconfig.json +33 -0
  196. package/tsconfig.spec.json +15 -0
  197. package/vercel.json +6 -0
  198. package/fesm2022/ng-comps.mjs +0 -2479
  199. package/fesm2022/ng-comps.mjs.map +0 -1
  200. package/types/ng-comps.d.ts +0 -917
@@ -0,0 +1,334 @@
1
+ import type { Meta, StoryObj } from '@storybook/angular';
2
+ import { Component, signal } from '@angular/core';
3
+ import { MfSidenavComponent, MfSidenavNavItem } from '../app/components/sidenav';
4
+
5
+ const NAV_ITEMS: MfSidenavNavItem[] = [
6
+ { id: 'home', icon: 'home', label: 'Inicio', active: true },
7
+ { id: 'dashboard', icon: 'dashboard', label: 'Dashboard' },
8
+ { id: 'analytics', icon: 'bar_chart', label: 'Analíticas', badge: 3 },
9
+ { id: 'users', icon: 'group', label: 'Usuarios' },
10
+ { id: 'messages', icon: 'mail', label: 'Mensajes', badge: 12 },
11
+ { id: 'settings', icon: 'settings', label: 'Configuración' },
12
+ { id: 'archived', icon: 'archive', label: 'Archivados', disabled: true },
13
+ ];
14
+
15
+ const meta: Meta<MfSidenavComponent> = {
16
+ title: 'Organisms/MfSidenav',
17
+ component: MfSidenavComponent,
18
+ tags: ['autodocs'],
19
+ parameters: {
20
+ layout: 'fullscreen',
21
+ docs: {
22
+ description: {
23
+ component: `
24
+ **MfSidenav** es el panel lateral de la librería ng-comps.
25
+ Envuelve Angular Material \`mat-sidenav-container\` con estilo propio: iconos Material,
26
+ estados activo/deshabilitado, badges de notificación y cabecera personalizable.
27
+
28
+ **Dos formas de uso:**
29
+
30
+ 1. **Navitems declarativos** — Proporciona \`navItems\`, \`headerTitle\` y \`headerIcon\`.
31
+ 2. **Content projection** — Proyecta \`[mfSidenavContent]\` para control total del contenido del panel.
32
+
33
+ El contenido principal siempre se proyecta sin atributo (slot por defecto).
34
+
35
+ | Propiedad | Descripción |
36
+ |----------------|---------------------------------------------------------|
37
+ | \`navItems\` | Array de ítems \`{ id, icon, label, active?, disabled?, badge? }\` |
38
+ | \`headerTitle\` | Título de la cabecera del sidenav |
39
+ | \`headerIcon\` | Icono Material de la cabecera |
40
+ | \`opened\` | Abierto o cerrado |
41
+ | \`mode\` | \`side\` · \`over\` · \`push\` |
42
+ | \`position\` | \`start\` (izquierda) · \`end\` (derecha) |
43
+ | \`sidenavWidth\` | Ancho del panel (valor CSS) |
44
+ `,
45
+ },
46
+ },
47
+ },
48
+ argTypes: {
49
+ opened: { control: 'boolean', description: 'Abierto o cerrado' },
50
+ mode: {
51
+ control: 'select',
52
+ options: ['side', 'over', 'push'],
53
+ description: 'Modo de apertura',
54
+ },
55
+ position: {
56
+ control: 'select',
57
+ options: ['start', 'end'],
58
+ description: 'Posición del panel',
59
+ },
60
+ sidenavWidth: { control: 'text', description: 'Ancho del panel lateral' },
61
+ headerTitle: { control: 'text', description: 'Título de la cabecera' },
62
+ headerIcon: { control: 'text', description: 'Icono Material de la cabecera' },
63
+ mfOpenedChange: { action: 'mfOpenedChange' },
64
+ mfNavItemClick: { action: 'mfNavItemClick' },
65
+ },
66
+ };
67
+
68
+ export default meta;
69
+ type Story = StoryObj<MfSidenavComponent>;
70
+
71
+ // ── Story helpers ─────────────────────────────────────────────────────────
72
+ const mainContent = `
73
+ <div style="padding: 24px;">
74
+ <h2 style="margin: 0 0 8px; font-size: 1.25rem; color: var(--mf-color-on-surface);">Contenido principal</h2>
75
+ <p style="color: var(--mf-color-neutral-600); font-size: 0.875rem; margin: 0;">
76
+ El panel lateral se renderiza a la izquierda. Puedes interactuar con los ítems del menú.
77
+ </p>
78
+ </div>
79
+ `;
80
+
81
+ export const Default: Story = {
82
+ name: 'Con navItems (modo side)',
83
+ args: {
84
+ opened: true,
85
+ mode: 'side',
86
+ headerTitle: 'MF App',
87
+ headerIcon: 'apps',
88
+ navItems: NAV_ITEMS,
89
+ },
90
+ render: (args) => ({
91
+ props: args,
92
+ template: `
93
+ <div style="height: 480px;">
94
+ <mf-sidenav
95
+ [opened]="opened"
96
+ [mode]="mode"
97
+ [position]="position"
98
+ [navItems]="navItems"
99
+ [headerTitle]="headerTitle"
100
+ [headerIcon]="headerIcon"
101
+ [sidenavWidth]="sidenavWidth || '260px'"
102
+ (mfNavItemClick)="mfNavItemClick($event)"
103
+ (mfOpenedChange)="mfOpenedChange($event)"
104
+ >
105
+ ${mainContent}
106
+ </mf-sidenav>
107
+ </div>
108
+ `,
109
+ moduleMetadata: { imports: [MfSidenavComponent] },
110
+ }),
111
+ };
112
+
113
+ export const Over: Story = {
114
+ name: 'Modo over',
115
+ args: {
116
+ opened: true,
117
+ mode: 'over',
118
+ headerTitle: 'MF App',
119
+ headerIcon: 'apps',
120
+ navItems: NAV_ITEMS,
121
+ },
122
+ render: (args) => ({
123
+ props: args,
124
+ template: `
125
+ <div style="height: 480px;">
126
+ <mf-sidenav
127
+ [opened]="opened"
128
+ [mode]="mode"
129
+ [navItems]="navItems"
130
+ [headerTitle]="headerTitle"
131
+ [headerIcon]="headerIcon"
132
+ (mfNavItemClick)="mfNavItemClick($event)"
133
+ >
134
+ ${mainContent}
135
+ </mf-sidenav>
136
+ </div>
137
+ `,
138
+ moduleMetadata: { imports: [MfSidenavComponent] },
139
+ }),
140
+ };
141
+
142
+ export const EndPosition: Story = {
143
+ name: 'Posición derecha',
144
+ args: {
145
+ opened: true,
146
+ mode: 'side',
147
+ position: 'end',
148
+ headerTitle: 'Detalles',
149
+ headerIcon: 'info',
150
+ navItems: [
151
+ { id: 'notifications', icon: 'notifications', label: 'Notificaciones', badge: 5, active: true },
152
+ { id: 'messages', icon: 'mail', label: 'Mensajes', badge: 2 },
153
+ { id: 'activity', icon: 'history', label: 'Actividad reciente' },
154
+ { id: 'bookmarks', icon: 'bookmark', label: 'Guardados' },
155
+ ],
156
+ },
157
+ render: (args) => ({
158
+ props: args,
159
+ template: `
160
+ <div style="height: 480px;">
161
+ <mf-sidenav
162
+ [opened]="opened"
163
+ [mode]="mode"
164
+ [position]="position"
165
+ [navItems]="navItems"
166
+ [headerTitle]="headerTitle"
167
+ [headerIcon]="headerIcon"
168
+ (mfNavItemClick)="mfNavItemClick($event)"
169
+ >
170
+ ${mainContent}
171
+ </mf-sidenav>
172
+ </div>
173
+ `,
174
+ moduleMetadata: { imports: [MfSidenavComponent] },
175
+ }),
176
+ };
177
+
178
+ export const Closed: Story = {
179
+ name: 'Cerrado',
180
+ args: {
181
+ opened: false,
182
+ mode: 'over',
183
+ headerTitle: 'MF App',
184
+ headerIcon: 'apps',
185
+ navItems: NAV_ITEMS,
186
+ },
187
+ render: (args) => ({
188
+ props: args,
189
+ template: `
190
+ <div style="height: 480px;">
191
+ <mf-sidenav
192
+ [opened]="opened"
193
+ [mode]="mode"
194
+ [navItems]="navItems"
195
+ [headerTitle]="headerTitle"
196
+ [headerIcon]="headerIcon"
197
+ >
198
+ <div style="padding: 24px;">
199
+ <p style="color: var(--mf-color-neutral-600); font-size: 0.875rem;">
200
+ El sidenav está cerrado. En modo <code>over</code> se superpone al abrirse.
201
+ </p>
202
+ </div>
203
+ </mf-sidenav>
204
+ </div>
205
+ `,
206
+ moduleMetadata: { imports: [MfSidenavComponent] },
207
+ }),
208
+ };
209
+
210
+ // ── Interactive story con toggle ──────────────────────────────────────────
211
+ @Component({
212
+ selector: 'mf-sidenav-interactive-demo',
213
+ imports: [MfSidenavComponent],
214
+ template: `
215
+ <div style="height: 520px; display: flex; flex-direction: column;">
216
+ <div style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--mf-color-border); background: var(--mf-color-surface); flex-shrink: 0;">
217
+ <button
218
+ type="button"
219
+ (click)="toggle()"
220
+ style="display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px; border: 1px solid var(--mf-color-border); border-radius: 9999px; background: var(--mf-color-surface); font-family: var(--mf-font-base); font-size: 0.875rem; font-weight: 500; cursor: pointer; color: var(--mf-color-on-surface);"
221
+ >
222
+ <span style="font-family: 'Material Icons'; font-size: 18px; line-height: 1;">menu</span>
223
+ {{ opened() ? 'Cerrar panel' : 'Abrir panel' }}
224
+ </button>
225
+ <span style="font-size: 0.875rem; color: var(--mf-color-neutral-400);">Panel: {{ opened() ? 'abierto' : 'cerrado' }}</span>
226
+ </div>
227
+ <div style="flex: 1; min-height: 0;">
228
+ <mf-sidenav
229
+ [opened]="opened()"
230
+ mode="over"
231
+ headerTitle="MF App"
232
+ headerIcon="apps"
233
+ [navItems]="items()"
234
+ (mfOpenedChange)="opened.set($event)"
235
+ (mfNavItemClick)="onNavItemClick($event)"
236
+ >
237
+ <div style="padding: 24px;">
238
+ <h2 style="margin: 0 0 8px; font-size: 1.25rem; color: var(--mf-color-on-surface);">{{ activeLabel() }}</h2>
239
+ <p style="color: var(--mf-color-neutral-600); font-size: 0.875rem; margin: 0;">
240
+ Haz clic en los ítems del menú para cambiar la sección activa.
241
+ </p>
242
+ </div>
243
+ </mf-sidenav>
244
+ </div>
245
+ </div>
246
+ `,
247
+ })
248
+ class MfSidenavInteractiveDemoComponent {
249
+ readonly opened = signal(false);
250
+ readonly activeId = signal('home');
251
+
252
+ readonly items = signal<MfSidenavNavItem[]>([
253
+ { id: 'home', icon: 'home', label: 'Inicio', active: true },
254
+ { id: 'dashboard', icon: 'dashboard', label: 'Dashboard' },
255
+ { id: 'analytics', icon: 'bar_chart', label: 'Analíticas', badge: 3 },
256
+ { id: 'users', icon: 'group', label: 'Usuarios' },
257
+ { id: 'messages', icon: 'mail', label: 'Mensajes', badge: 12 },
258
+ { id: 'settings', icon: 'settings', label: 'Configuración' },
259
+ ]);
260
+
261
+ readonly activeLabel = () =>
262
+ this.items().find((i) => i.id === this.activeId())?.label ?? 'Inicio';
263
+
264
+ toggle(): void {
265
+ this.opened.set(!this.opened());
266
+ }
267
+
268
+ onNavItemClick(item: MfSidenavNavItem): void {
269
+ this.activeId.set(item.id);
270
+ this.items.set(
271
+ this.items().map((i) => ({ ...i, active: i.id === item.id }))
272
+ );
273
+ this.opened.set(false);
274
+ }
275
+ }
276
+
277
+ export const Interactive: Story = {
278
+ name: 'Interactivo (toggle + selección)',
279
+ render: () => ({
280
+ template: `<mf-sidenav-interactive-demo />`,
281
+ moduleMetadata: { imports: [MfSidenavInteractiveDemoComponent] },
282
+ }),
283
+ parameters: {
284
+ docs: {
285
+ description: {
286
+ story:
287
+ 'Panel en modo `over` con botón de toggle y actualización del ítem activo al hacer clic.',
288
+ },
289
+ },
290
+ },
291
+ };
292
+
293
+ export const ContentProjection: Story = {
294
+ name: 'Content projection (personalizado)',
295
+ render: () => ({
296
+ template: `
297
+ <div style="height: 480px;">
298
+ <mf-sidenav opened="true" mode="side" sidenavWidth="240px">
299
+ <div mfSidenavContent style="display: flex; flex-direction: column; height: 100%;">
300
+ <div style="padding: 20px 16px 16px; border-bottom: 1px solid var(--mf-color-border);">
301
+ <span style="font-family: var(--mf-font-display); font-weight: 700; color: var(--mf-color-brand);">Área de trabajo</span>
302
+ </div>
303
+ <nav style="flex: 1; padding: 12px; display: flex; flex-direction: column; gap: 4px;">
304
+ <a href="#" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 10px; text-decoration: none; color: var(--mf-color-on-surface); font-size: 0.875rem; background: var(--mf-color-brand-light); color: var(--mf-color-brand); font-weight: 600;">
305
+ <span class="material-icons" style="font-size: 20px;">star</span> Destacados
306
+ </a>
307
+ <a href="#" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 10px; text-decoration: none; color: var(--mf-color-neutral-600); font-size: 0.875rem;">
308
+ <span class="material-icons" style="font-size: 20px;">folder</span> Proyectos
309
+ </a>
310
+ <a href="#" style="display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 10px; text-decoration: none; color: var(--mf-color-neutral-600); font-size: 0.875rem;">
311
+ <span class="material-icons" style="font-size: 20px;">people</span> Equipo
312
+ </a>
313
+ </nav>
314
+ </div>
315
+ <div style="padding: 24px;">
316
+ <h2 style="margin: 0 0 8px; font-size: 1.25rem; color: var(--mf-color-on-surface);">Panel personalizado</h2>
317
+ <p style="color: var(--mf-color-neutral-600); font-size: 0.875rem; margin: 0;">
318
+ Usando <code>[mfSidenavContent]</code> para proyectar contenido completamente personalizado.
319
+ </p>
320
+ </div>
321
+ </mf-sidenav>
322
+ </div>
323
+ `,
324
+ moduleMetadata: { imports: [MfSidenavComponent] },
325
+ }),
326
+ parameters: {
327
+ docs: {
328
+ description: {
329
+ story:
330
+ 'Cuando no se proporciona `navItems`, se activa el slot `[mfSidenavContent]` para proyectar HTML personalizado.',
331
+ },
332
+ },
333
+ },
334
+ };
@@ -0,0 +1,80 @@
1
+ import type { Meta, StoryObj } from '@storybook/angular';
2
+
3
+ import { MfTableComponent } from '../app/components/table';
4
+
5
+ const COLUMNS = [
6
+ { key: 'name', header: 'Nombre', sortable: true },
7
+ { key: 'role', header: 'Rol' },
8
+ { key: 'status', header: 'Estado' },
9
+ ];
10
+
11
+ const DATA = [
12
+ { name: 'Ana Torres', role: 'Frontend', status: 'Activa' },
13
+ { name: 'Luis Vega', role: 'Design Ops', status: 'Pendiente' },
14
+ { name: 'Marta Solis', role: 'QA', status: 'Activa' },
15
+ ];
16
+
17
+ const meta: Meta<MfTableComponent> = {
18
+ title: 'Organisms/MfTable',
19
+ component: MfTableComponent,
20
+ tags: ['autodocs'],
21
+ parameters: {
22
+ docs: {
23
+ description: {
24
+ component: `
25
+ **MfTable** renders data with native table semantics and explicit row actions.
26
+
27
+ The v1 contract avoids clickable rows as the default interaction pattern. If a row needs actions, render focusable controls inside cells and give them contextual accessible names.
28
+ `,
29
+ },
30
+ },
31
+ },
32
+ argTypes: {
33
+ variant: {
34
+ control: 'select',
35
+ options: ['default', 'striped', 'bordered'],
36
+ },
37
+ rowActionLabel: { control: 'text', description: 'Visible label for the row action button' },
38
+ rowActionHeader: { control: 'text', description: 'Header text for the action column' },
39
+ mfSortChange: { action: 'mfSortChange' },
40
+ mfRowAction: { action: 'mfRowAction' },
41
+ },
42
+ };
43
+
44
+ export default meta;
45
+ type Story = StoryObj<MfTableComponent>;
46
+
47
+ export const Default: Story = {
48
+ args: {
49
+ columns: COLUMNS,
50
+ data: DATA,
51
+ },
52
+ };
53
+
54
+ export const WithExplicitActions: Story = {
55
+ name: 'With Explicit Actions',
56
+ args: {
57
+ columns: COLUMNS,
58
+ data: DATA,
59
+ rowActionLabel: 'Ver detalle',
60
+ rowActionHeader: 'Acciones',
61
+ rowActionAriaLabel: (row) => `Ver detalle de ${String(row['name'])}`,
62
+ },
63
+ };
64
+
65
+ export const IncorrectUsage: Story = {
66
+ name: 'Incorrect Usage',
67
+ render: () => ({
68
+ template: `
69
+ <section style="max-width: 720px; border: 1px solid var(--mf-color-border); border-radius: 16px; padding: 20px; background: var(--mf-color-surface);">
70
+ <h2 style="margin: 0 0 12px; font-size: 1rem;">Anti-patrones</h2>
71
+ <ul style="margin: 0 0 16px; padding-left: 18px;">
72
+ <li>Hacer clickable toda la fila con \`(click)\` en \`tr\`.</li>
73
+ <li>Usar color como unica senal de estado.</li>
74
+ <li>Repetir el mismo nombre accesible en todas las acciones de fila.</li>
75
+ </ul>
76
+ <pre style="margin: 0; padding: 16px; border-radius: 12px; background: #111827; color: #f9fafb; overflow: auto;"><code>&lt;tr (click)="openDetail(row)"&gt;...&lt;/tr&gt;</code></pre>
77
+ </section>
78
+ `,
79
+ }),
80
+ };
@@ -0,0 +1,112 @@
1
+ import type { Meta, StoryObj } from '@storybook/angular';
2
+
3
+ import { MfToolbarComponent } from '../app/components/toolbar';
4
+ import { MfButtonComponent } from '../app/components/button';
5
+ import { MfIconComponent } from '../app/components/icon';
6
+
7
+ const meta: Meta<MfToolbarComponent> = {
8
+ title: 'Organisms/MfToolbar',
9
+ component: MfToolbarComponent,
10
+ tags: ['autodocs'],
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component: `
15
+ **MfToolbar** es la barra de herramientas de la librería ng-comps.
16
+ Usa Angular Material \`mat-toolbar\` por debajo pero con estilo propio: minimalista y elegante.
17
+
18
+ | Variante | Cuándo usarla |
19
+ |----------------|------------------------------------------------|
20
+ | \`surface\` | Toolbar por defecto, fondo blanco |
21
+ | \`brand\` | Toolbar con color de marca |
22
+ | \`transparent\` | Sobre fondos con imagen o gradiente |
23
+ `,
24
+ },
25
+ },
26
+ },
27
+ argTypes: {
28
+ title: { control: 'text', description: 'Título de la toolbar' },
29
+ variant: {
30
+ control: 'select',
31
+ options: ['surface', 'brand', 'transparent'],
32
+ description: 'Variante visual',
33
+ },
34
+ bordered: { control: 'boolean', description: 'Borde inferior' },
35
+ sticky: { control: 'boolean', description: 'Posición fija' },
36
+ elevated: { control: 'boolean', description: 'Sombra sutil' },
37
+ },
38
+ };
39
+
40
+ export default meta;
41
+ type Story = StoryObj<MfToolbarComponent>;
42
+
43
+ export const Surface: Story = {
44
+ render: (args) => ({
45
+ props: args,
46
+ template: `
47
+ <mf-toolbar [title]="title" [variant]="variant" [bordered]="bordered">
48
+ <mf-button mfToolbarEnd label="Iniciar sesión" variant="outlined" size="sm" />
49
+ <mf-button mfToolbarEnd label="Registrarse" variant="filled" size="sm" />
50
+ </mf-toolbar>
51
+ `,
52
+ moduleMetadata: { imports: [MfToolbarComponent, MfButtonComponent] },
53
+ }),
54
+ args: {
55
+ title: 'MF Components',
56
+ variant: 'surface',
57
+ bordered: true,
58
+ },
59
+ };
60
+
61
+ export const Brand: Story = {
62
+ render: (args) => ({
63
+ props: args,
64
+ template: `
65
+ <mf-toolbar [title]="title" variant="brand" [bordered]="bordered">
66
+ <mf-button mfToolbarEnd label="Dashboard" variant="text" size="sm" />
67
+ <mf-button mfToolbarEnd label="Configuración" variant="text" size="sm" />
68
+ </mf-toolbar>
69
+ `,
70
+ moduleMetadata: { imports: [MfToolbarComponent, MfButtonComponent] },
71
+ }),
72
+ args: {
73
+ title: 'Dashboard',
74
+ bordered: true,
75
+ },
76
+ };
77
+
78
+ export const WithIcons: Story = {
79
+ name: 'Con iconos',
80
+ render: () => ({
81
+ template: `
82
+ <mf-toolbar title="Mi Aplicación" variant="surface" [bordered]="true" [elevated]="true">
83
+ <mf-icon mfToolbarEnd name="notifications" color="muted" />
84
+ <mf-icon mfToolbarEnd name="settings" color="muted" />
85
+ <mf-icon mfToolbarEnd name="account_circle" size="lg" color="brand" />
86
+ </mf-toolbar>
87
+ `,
88
+ moduleMetadata: { imports: [MfToolbarComponent, MfIconComponent] },
89
+ }),
90
+ };
91
+
92
+ export const Elevated: Story = {
93
+ render: () => ({
94
+ template: `
95
+ <mf-toolbar title="Con elevación" [elevated]="true" [bordered]="false">
96
+ <mf-button mfToolbarEnd label="Acción" variant="filled" size="sm" />
97
+ </mf-toolbar>
98
+ `,
99
+ moduleMetadata: { imports: [MfToolbarComponent, MfButtonComponent] },
100
+ }),
101
+ };
102
+
103
+ export const Transparent: Story = {
104
+ render: () => ({
105
+ template: `
106
+ <div style="background: linear-gradient(135deg, var(--mf-color-primary-700), var(--mf-color-primary-900)); padding: 0; border-radius: var(--mf-radius-lg);">
107
+ <mf-toolbar title="Sobre gradiente" variant="transparent" [bordered]="false" />
108
+ </div>
109
+ `,
110
+ moduleMetadata: { imports: [MfToolbarComponent] },
111
+ }),
112
+ };
@@ -0,0 +1,3 @@
1
+ export interface User {
2
+ name: string;
3
+ }
package/src/styles.css CHANGED
@@ -7,30 +7,56 @@
7
7
  @import './theme/tokens.css';
8
8
 
9
9
  /* 2. Fuentes vía Google Fonts (sólo en desarrollo; en prod usar asset local) */
10
- @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@500;700&display=swap');
10
+ @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@500;700&display=swap');
11
11
 
12
12
  /* 3. Reset y base */
13
13
  *, *::before, *::after {
14
14
  box-sizing: border-box;
15
15
  }
16
16
 
17
- body {
18
- margin: 0;
19
- font-family: var(--mf-font-base);
20
- font-size: var(--mf-text-base);
21
- line-height: var(--mf-leading-normal);
22
- color: var(--mf-color-on-surface);
23
- background-color: var(--mf-color-surface);
24
- -webkit-font-smoothing: antialiased;
25
- -moz-osx-font-smoothing: grayscale;
26
- }
17
+ body {
18
+ margin: 0;
19
+ font-family: var(--mf-font-base);
20
+ font-size: var(--mf-text-base);
21
+ line-height: var(--mf-leading-normal);
22
+ color: var(--mf-color-on-surface);
23
+ background-color: var(--mf-color-surface);
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
26
+ }
27
+
28
+ :where(
29
+ button,
30
+ [href],
31
+ input,
32
+ select,
33
+ textarea,
34
+ summary,
35
+ [tabindex]:not([tabindex='-1'])
36
+ ):focus-visible {
37
+ outline: var(--mf-focus-ring-width) solid var(--mf-focus-ring-color) !important;
38
+ outline-offset: var(--mf-focus-ring-offset);
39
+ }
40
+
41
+ .mat-mdc-option.mdc-list-item--focused,
42
+ .mat-mdc-option.mat-mdc-option-active,
43
+ .mat-mdc-menu-item.cdk-keyboard-focused,
44
+ .mat-mdc-menu-item.cdk-program-focused,
45
+ .mat-mdc-paginator-navigation-previous:focus-visible,
46
+ .mat-mdc-paginator-navigation-next:focus-visible,
47
+ .mat-mdc-paginator-navigation-first:focus-visible,
48
+ .mat-mdc-paginator-navigation-last:focus-visible,
49
+ .mat-datepicker-toggle button:focus-visible {
50
+ outline: var(--mf-focus-ring-width) solid var(--mf-focus-ring-color) !important;
51
+ outline-offset: var(--mf-focus-ring-offset);
52
+ }
27
53
 
28
54
  /* 4. Override de tokens de Material para usar nuestra paleta */
29
- :root {
30
- /* Botones */
31
- --mdc-filled-button-container-color: var(--mf-color-brand);
32
- --mdc-filled-button-label-text-color: var(--mf-color-on-brand);
33
- --mdc-filled-button-container-shape: var(--mf-radius-full);
55
+ :root {
56
+ /* Botones */
57
+ --mdc-filled-button-container-color: var(--mf-color-brand);
58
+ --mdc-filled-button-label-text-color: var(--mf-color-on-brand);
59
+ --mdc-filled-button-container-shape: var(--mf-radius-full);
34
60
 
35
61
  --mdc-outlined-button-container-shape: var(--mf-radius-full);
36
62
  --mdc-outlined-button-label-text-color: var(--mf-color-secondary-900);
@@ -175,8 +201,19 @@ body {
175
201
  color: var(--mf-color-brand) !important;
176
202
  }
177
203
 
178
- .mf-autocomplete-panel .mat-mdc-option.mat-mdc-option-active {
179
- background-color: var(--mf-color-brand-light) !important;
180
- color: var(--mf-color-brand) !important;
181
- font-weight: var(--mf-weight-medium) !important;
182
- }
204
+ .mf-autocomplete-panel .mat-mdc-option.mat-mdc-option-active {
205
+ background-color: var(--mf-color-brand-light) !important;
206
+ color: var(--mf-color-brand) !important;
207
+ font-weight: var(--mf-weight-medium) !important;
208
+ }
209
+
210
+ .mf-input__error,
211
+ .mf-autocomplete__error,
212
+ .mf-select__error,
213
+ .mf-datepicker__error,
214
+ .mf-textarea__error {
215
+ margin: var(--mf-space-1) var(--mf-space-4) 0;
216
+ font-size: var(--mf-text-xs);
217
+ line-height: var(--mf-leading-normal);
218
+ color: var(--mf-color-error-500);
219
+ }
@@ -30,15 +30,15 @@
30
30
  --mf-color-accent-500: #f59e0b; /* default */
31
31
  --mf-color-accent-700: #b45309;
32
32
 
33
- --mf-color-error-500: #ef4444;
34
- --mf-color-error-700: #b91c1c;
33
+ --mf-color-error-500: #dc2626;
34
+ --mf-color-error-700: #b91c1c;
35
35
 
36
36
  --mf-color-neutral-0: #ffffff;
37
37
  --mf-color-neutral-50: #f7fafc;
38
38
  --mf-color-neutral-100: #f1f5f9;
39
39
  --mf-color-neutral-200: #e2e8f0;
40
40
  --mf-color-neutral-300: #cbd5e1;
41
- --mf-color-neutral-400: #94a3b8;
41
+ --mf-color-neutral-400: #64748b;
42
42
  --mf-color-neutral-600: #475569;
43
43
  --mf-color-neutral-800: #1e293b;
44
44
  --mf-color-neutral-900: #0f1722;
@@ -51,7 +51,10 @@
51
51
  --mf-color-on-surface: var(--mf-color-neutral-800);
52
52
  --mf-color-surface: var(--mf-color-neutral-0);
53
53
  --mf-color-surface-raised: var(--mf-color-neutral-50);
54
- --mf-color-border: var(--mf-color-neutral-200);
54
+ --mf-color-border: var(--mf-color-neutral-200);
55
+ --mf-focus-ring-color: var(--mf-color-secondary-700);
56
+ --mf-focus-ring-offset: 2px;
57
+ --mf-focus-ring-width: 3px;
55
58
 
56
59
  /* ── Tipografía ─────────────────────────────────────────────── */
57
60
  --mf-font-base: 'Manrope', 'Segoe UI', system-ui, sans-serif;
@@ -0,0 +1,15 @@
1
+ /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2
+ /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3
+ {
4
+ "extends": "./tsconfig.json",
5
+ "compilerOptions": {
6
+ "outDir": "./out-tsc/app",
7
+ "types": []
8
+ },
9
+ "include": [
10
+ "src/**/*.ts"
11
+ ],
12
+ "exclude": [
13
+ "src/**/*.spec.ts"
14
+ ]
15
+ }