@xsmusices/ui 1.43.1 → 1.44.2

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.
@@ -0,0 +1,284 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, signal, ChangeDetectionStrategy, Component, inject } from '@angular/core';
3
+ import { XSModalService, XSModalImageSearchComponent, XSButtonComponent } from './xsmusices-ui.mjs';
4
+ import * as i1 from '@angular/forms';
5
+ import { FormsModule } from '@angular/forms';
6
+
7
+ /**
8
+ * Contenido de ejemplo para el modo side-panel de xs-modal.
9
+ * Formulario largo con textarea para validar scroll interno,
10
+ * responsive (pantalla completa en móvil) y acciones de guardar/cancelar.
11
+ */
12
+ class ModalSidePanelExampleComponent {
13
+ modalData = input(...(ngDevMode ? [undefined, { debugName: "modalData" }] : []));
14
+ modalResult = output();
15
+ artistName = signal('', ...(ngDevMode ? [{ debugName: "artistName" }] : []));
16
+ infoEn = signal('', ...(ngDevMode ? [{ debugName: "infoEn" }] : []));
17
+ genre = signal('', ...(ngDevMode ? [{ debugName: "genre" }] : []));
18
+ savedResult = signal(null, ...(ngDevMode ? [{ debugName: "savedResult" }] : []));
19
+ constructor() {
20
+ const data = this.modalData();
21
+ if (data?.artistName)
22
+ this.artistName.set(data.artistName);
23
+ if (data?.infoEn)
24
+ this.infoEn.set(data.infoEn);
25
+ }
26
+ footerButtons() {
27
+ return [
28
+ {
29
+ id: 'sidePanelCancel',
30
+ label: 'Cancelar',
31
+ icon: { name: 'cancel' },
32
+ action: 'cancel',
33
+ },
34
+ {
35
+ id: 'sidePanelSave',
36
+ label: 'Guardar',
37
+ icon: { name: 'check' },
38
+ action: 'save',
39
+ },
40
+ ];
41
+ }
42
+ submit(action) {
43
+ if (action === 'save') {
44
+ const data = {
45
+ artistName: this.artistName(),
46
+ infoEn: this.infoEn(),
47
+ };
48
+ this.savedResult.set(`Guardado: ${data.artistName ?? '(sin nombre)'}`);
49
+ this.modalResult.emit({ action: 'save', data });
50
+ }
51
+ else {
52
+ this.modalResult.emit(null);
53
+ }
54
+ }
55
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: ModalSidePanelExampleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
56
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: ModalSidePanelExampleComponent, isStandalone: true, selector: "modal-side-panel-example", inputs: { modalData: { classPropertyName: "modalData", publicName: "modalData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modalResult: "modalResult" }, host: { attributes: { "modal-side-panel-example-host": "" } }, ngImport: i0, template: "<div class=\"flex flex-col gap-4 p-1\">\n <p class=\"text-sm text-zinc-400\">\n Ejemplo de panel lateral con formulario extenso. El contenido\n debe desplazarse internamente cuando sea largo y el panel debe\n ocupar toda la pantalla en m\u00F3vil.\n </p>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Nombre del artista\n <input\n class=\"rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n type=\"text\"\n placeholder=\"Ej. Aphex Twin\"\n [ngModel]=\"artistName()\"\n (ngModelChange)=\"artistName.set($event)\"\n />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n G\u00E9nero\n <input\n class=\"rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n type=\"text\"\n placeholder=\"Ej. Electronic\"\n [ngModel]=\"genre()\"\n (ngModelChange)=\"genre.set($event)\"\n />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Biograf\u00EDa (infoEn)\n <textarea\n class=\"min-h-40 rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n rows=\"8\"\n placeholder=\"Pega aqu\u00ED una biograf\u00EDa larga para probar el scroll y el teclado virtual\u2026\"\n [ngModel]=\"infoEn()\"\n (ngModelChange)=\"infoEn.set($event)\"\n ></textarea>\n </label>\n\n @for (\n block of [\n 'Bloque de relleno 1: contenido largo para forzar el scroll interno del panel.',\n 'Bloque de relleno 2: verifica que la cabecera y las acciones sigan accesibles.',\n 'Bloque de relleno 3: en m\u00F3vil no debe haber scroll horizontal ni contenido cortado.',\n 'Bloque de relleno 4: al cerrar se restaura el scroll de la p\u00E1gina original.',\n ];\n track $index\n ) {\n <p class=\"rounded-lg bg-zinc-900 p-3 text-sm text-zinc-400\">\n {{ block }}\n </p>\n }\n\n @if (savedResult()) {\n <p class=\"rounded-lg bg-zinc-800 p-3 text-sm text-emerald-400\">\n {{ savedResult() }}\n </p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
57
+ }
58
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: ModalSidePanelExampleComponent, decorators: [{
59
+ type: Component,
60
+ args: [{ selector: 'modal-side-panel-example', host: { 'modal-side-panel-example-host': '' }, changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormsModule], template: "<div class=\"flex flex-col gap-4 p-1\">\n <p class=\"text-sm text-zinc-400\">\n Ejemplo de panel lateral con formulario extenso. El contenido\n debe desplazarse internamente cuando sea largo y el panel debe\n ocupar toda la pantalla en m\u00F3vil.\n </p>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Nombre del artista\n <input\n class=\"rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n type=\"text\"\n placeholder=\"Ej. Aphex Twin\"\n [ngModel]=\"artistName()\"\n (ngModelChange)=\"artistName.set($event)\"\n />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n G\u00E9nero\n <input\n class=\"rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n type=\"text\"\n placeholder=\"Ej. Electronic\"\n [ngModel]=\"genre()\"\n (ngModelChange)=\"genre.set($event)\"\n />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Biograf\u00EDa (infoEn)\n <textarea\n class=\"min-h-40 rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n rows=\"8\"\n placeholder=\"Pega aqu\u00ED una biograf\u00EDa larga para probar el scroll y el teclado virtual\u2026\"\n [ngModel]=\"infoEn()\"\n (ngModelChange)=\"infoEn.set($event)\"\n ></textarea>\n </label>\n\n @for (\n block of [\n 'Bloque de relleno 1: contenido largo para forzar el scroll interno del panel.',\n 'Bloque de relleno 2: verifica que la cabecera y las acciones sigan accesibles.',\n 'Bloque de relleno 3: en m\u00F3vil no debe haber scroll horizontal ni contenido cortado.',\n 'Bloque de relleno 4: al cerrar se restaura el scroll de la p\u00E1gina original.',\n ];\n track $index\n ) {\n <p class=\"rounded-lg bg-zinc-900 p-3 text-sm text-zinc-400\">\n {{ block }}\n </p>\n }\n\n @if (savedResult()) {\n <p class=\"rounded-lg bg-zinc-800 p-3 text-sm text-emerald-400\">\n {{ savedResult() }}\n </p>\n }\n</div>\n" }]
61
+ }], ctorParameters: () => [], propDecorators: { modalData: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalData", required: false }] }], modalResult: [{ type: i0.Output, args: ["modalResult"] }] } });
62
+
63
+ class ModalExampleComponent {
64
+ activeModal = input(...(ngDevMode ? [undefined, { debugName: "activeModal" }] : []));
65
+ modalService = inject(XSModalService);
66
+ openConfirm() {
67
+ this.modalService
68
+ .openConfirm({
69
+ title: 'Confirmar eliminación',
70
+ subTitle: '¿Estás seguro de que quieres eliminar este elemento?',
71
+ labelAccept: 'Sí, eliminar',
72
+ labelCancel: 'Cancelar',
73
+ type: 'centered',
74
+ })
75
+ .subscribe((_result) => { });
76
+ }
77
+ openInfo() {
78
+ this.modalService
79
+ .openInfo({
80
+ title: 'Información importante',
81
+ info: '<p>Este es un mensaje <strong>importante</strong> que el usuario debe leer.</p><p>Contiene información crítica sobre el sistema.</p>',
82
+ labelClose: 'Entendido',
83
+ type: 'centered',
84
+ })
85
+ .subscribe(() => { });
86
+ }
87
+ openInput() {
88
+ this.modalService
89
+ .openInput({
90
+ title: 'Ingrese su nombre',
91
+ subTitle: 'Necesitamos su nombre para continuar',
92
+ placeholder: 'Escriba su nombre aquí...',
93
+ minLength: 2,
94
+ maxLength: 50,
95
+ labelAccept: 'Guardar',
96
+ labelCancel: 'Cancelar',
97
+ type: 'centered',
98
+ })
99
+ .subscribe((_result) => { });
100
+ }
101
+ openClaim() {
102
+ this.modalService
103
+ .openClaim({
104
+ title: 'Reclamar elemento',
105
+ subTitle: 'Esta acción requiere justificación',
106
+ warningMsg: '¿Estás seguro de que quieres reclamar este elemento?',
107
+ enterReason: 'Por favor explica el motivo de la reclamación',
108
+ labelAccept: 'Reclamar',
109
+ labelCancel: 'Cancelar',
110
+ type: 'centered',
111
+ })
112
+ .subscribe((_result) => { });
113
+ }
114
+ openImageSearch() {
115
+ this.modalService
116
+ .open(XSModalImageSearchComponent, {
117
+ type: 'bottom-sheet',
118
+ title: 'Scraping de imágenes',
119
+ subTitle: 'Busca y selecciona una imagen',
120
+ data: {
121
+ q: 'music',
122
+ searchImages: async (query, limit) => {
123
+ const images = Array.from({ length: limit }, (_, i) => `https://picsum.photos/seed/${encodeURIComponent(query)}${i}/300/300`);
124
+ return { images };
125
+ },
126
+ },
127
+ })
128
+ .subscribe((_result) => { });
129
+ }
130
+ openImageSearchWithSources() {
131
+ const mockSoundcloudSearch = async (query) => {
132
+ return {
133
+ images: [
134
+ `https://picsum.photos/seed/soundcloud-${encodeURIComponent(query)}/300/300`,
135
+ ],
136
+ };
137
+ };
138
+ const mockGoogleSearch = async (query, limit, start) => {
139
+ const totalResults = 15;
140
+ const offset = start ?? 1;
141
+ const images = Array.from({ length: Math.min(limit, totalResults - offset + 1) }, (_, i) => `https://picsum.photos/seed/${encodeURIComponent(query)}-g-${offset + i}/300/300`);
142
+ return { images, totalResults };
143
+ };
144
+ this.modalService
145
+ .open(XSModalImageSearchComponent, {
146
+ type: 'bottom-sheet',
147
+ title: 'Scraping de imágenes',
148
+ subTitle: 'Busca en Google Images y SoundCloud',
149
+ data: {
150
+ q: 'music',
151
+ sources: [
152
+ {
153
+ id: 'google',
154
+ label: 'Google Images',
155
+ searchImages: mockGoogleSearch,
156
+ },
157
+ {
158
+ id: 'soundcloud',
159
+ label: 'SoundCloud',
160
+ searchImages: mockSoundcloudSearch,
161
+ },
162
+ ],
163
+ },
164
+ })
165
+ .subscribe((_result) => { });
166
+ }
167
+ openSidePanel() {
168
+ this.modalService
169
+ .open(ModalSidePanelExampleComponent, {
170
+ type: 'side-panel',
171
+ title: 'Editar artista',
172
+ subTitle: 'Panel lateral (pantalla completa en móvil)',
173
+ data: {
174
+ artistName: 'Aphex Twin',
175
+ infoEn: 'Richard David James, known as Aphex Twin, is a British electronic musician.',
176
+ },
177
+ })
178
+ .subscribe((_result) => { });
179
+ }
180
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: ModalExampleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
181
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: ModalExampleComponent, isStandalone: true, selector: "modal-example", inputs: { activeModal: { classPropertyName: "activeModal", publicName: "activeModal", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "modal-example-host": "" } }, ngImport: i0, template: " <div class=\"p-4\">\n <div class=\"grid grid-cols-1 gap-4\">\n @if (activeModal() === 'confirm' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Confirmaci\u00F3n'\"\n (onClick)=\"openConfirm()\"\n ></xs-button>\n }\n @if (activeModal() === 'info' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Informaci\u00F3n'\"\n (onClick)=\"openInfo()\"\n ></xs-button>\n }\n @if (activeModal() === 'input' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Input'\"\n (onClick)=\"openInput()\"\n ></xs-button>\n }\n @if (activeModal() === 'claim' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Reclamo'\"\n (onClick)=\"openClaim()\"\n ></xs-button>\n }\n @if (\n activeModal() === 'imageSearch' || !activeModal()\n ) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de B\u00FAsqueda de Im\u00E1genes'\"\n (onClick)=\"openImageSearch()\"\n class=\"mb-2\"\n ></xs-button>\n }\n @if (\n activeModal() === 'imageSearchSources' ||\n !activeModal()\n ) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir con Google + SoundCloud'\"\n (onClick)=\"openImageSearchWithSources()\"\n ></xs-button>\n }\n @if (activeModal() === 'sidePanel' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Panel Lateral'\"\n (onClick)=\"openSidePanel()\"\n ></xs-button>\n }\n </div>\n </div>\n", dependencies: [{ kind: "component", type: XSButtonComponent, selector: "xs-button", inputs: ["id", "label", "type", "icon", "action", "customClass", "variant", "layout", "badgeCount", "isFull", "disabled", "loading", "title", "ariaLabel", "borderRadius", "isGrouped", "groupPosition"], outputs: ["isGroupedChange", "groupPositionChange", "click", "onClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
182
+ }
183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: ModalExampleComponent, decorators: [{
184
+ type: Component,
185
+ args: [{ selector: 'modal-example', host: { 'modal-example-host': '' }, changeDetection: ChangeDetectionStrategy.OnPush, imports: [XSButtonComponent], template: " <div class=\"p-4\">\n <div class=\"grid grid-cols-1 gap-4\">\n @if (activeModal() === 'confirm' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Confirmaci\u00F3n'\"\n (onClick)=\"openConfirm()\"\n ></xs-button>\n }\n @if (activeModal() === 'info' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Informaci\u00F3n'\"\n (onClick)=\"openInfo()\"\n ></xs-button>\n }\n @if (activeModal() === 'input' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Input'\"\n (onClick)=\"openInput()\"\n ></xs-button>\n }\n @if (activeModal() === 'claim' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Reclamo'\"\n (onClick)=\"openClaim()\"\n ></xs-button>\n }\n @if (\n activeModal() === 'imageSearch' || !activeModal()\n ) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de B\u00FAsqueda de Im\u00E1genes'\"\n (onClick)=\"openImageSearch()\"\n class=\"mb-2\"\n ></xs-button>\n }\n @if (\n activeModal() === 'imageSearchSources' ||\n !activeModal()\n ) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir con Google + SoundCloud'\"\n (onClick)=\"openImageSearchWithSources()\"\n ></xs-button>\n }\n @if (activeModal() === 'sidePanel' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Panel Lateral'\"\n (onClick)=\"openSidePanel()\"\n ></xs-button>\n }\n </div>\n </div>\n" }]
186
+ }], propDecorators: { activeModal: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeModal", required: false }] }] } });
187
+
188
+ const story = {
189
+ id: 'modal',
190
+ name: 'Modal',
191
+ type: 'component',
192
+ subtype: 'Base',
193
+ status: 'active',
194
+ usage: ['admin', 'app', 'monitor'],
195
+ updated: '2026-05-30T12:00:00.000Z',
196
+ created: '2025-07-29T15:06:52.383Z',
197
+ description: 'Componente Modal con diferentes tipos predefinidos y soporte multi-idioma automático con traducciones expandidas. ' +
198
+ 'Contrato de footer reactivo: el componente inyectado puede exponer footerButtons() (XSButtonI[]) y submit(action). ' +
199
+ 'El modal sincroniza los botones ante cada cambio (etiqueta, variante, disabled, loading) y enruta cancel→cierre, ' +
200
+ 'accept/save/delete/load→submit(action) si existe (si no, cierra con {action}) y cualquier otra acción→cierre con null. ' +
201
+ 'Aviso: una acción personalizada sin submit cierra el modal con null. ' +
202
+ 'El footer es persistente por construcción (flex, fuera del área de scroll con safe-area inferior) y es opcional: ' +
203
+ 'pasar buttons o exponer footerButtons() lo activa; omitirlos conserva el modal sin footer, sin cambios para los usos existentes.',
204
+ component: ModalExampleComponent,
205
+ selector: 'xs-modal',
206
+ multilanguage: true,
207
+ inputs: [
208
+ {
209
+ key: 'activeModal',
210
+ label: 'Modal activo',
211
+ type: 'select',
212
+ options: [
213
+ 'confirm',
214
+ 'info',
215
+ 'input',
216
+ 'claim',
217
+ 'imageSearch',
218
+ 'imageSearchSources',
219
+ 'sidePanel',
220
+ ],
221
+ default: null,
222
+ },
223
+ ],
224
+ examples: [
225
+ {
226
+ name: 'Confirm Modal',
227
+ description: 'Modal de confirmación para acciones del usuario',
228
+ inputs: {
229
+ activeModal: 'confirm',
230
+ },
231
+ },
232
+ {
233
+ name: 'Info Modal',
234
+ description: 'Modal de información para mostrar mensajes al usuario',
235
+ inputs: {
236
+ activeModal: 'info',
237
+ },
238
+ },
239
+ {
240
+ name: 'Input Modal',
241
+ description: 'Modal para solicitar entrada de datos del usuario',
242
+ inputs: {
243
+ activeModal: 'input',
244
+ },
245
+ },
246
+ {
247
+ name: 'Claim Modal',
248
+ description: 'Modal de reclamación con campo de justificación',
249
+ inputs: {
250
+ activeModal: 'claim',
251
+ },
252
+ },
253
+ {
254
+ name: 'Image Search Modal',
255
+ description: 'Modal de búsqueda de imágenes con input y resultados en grid',
256
+ inputs: {
257
+ activeModal: 'imageSearch',
258
+ },
259
+ },
260
+ {
261
+ name: 'Image Search con Google + SoundCloud',
262
+ description: 'Modal de búsqueda de imágenes con múltiples fuentes: Google Images y SoundCloud',
263
+ inputs: {
264
+ activeModal: 'imageSearchSources',
265
+ },
266
+ },
267
+ {
268
+ name: 'Side Panel',
269
+ description: 'Panel lateral derecho (pantalla completa en móvil) con formulario largo, textarea y scroll interno',
270
+ inputs: {
271
+ activeModal: 'sidePanel',
272
+ },
273
+ },
274
+ ],
275
+ coverage: {
276
+ statements: 38,
277
+ branches: 11,
278
+ functions: 45,
279
+ lines: 38,
280
+ },
281
+ };
282
+
283
+ export { story };
284
+ //# sourceMappingURL=xsmusices-ui-xs-modal.xsstory-DJxKOKsQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xsmusices-ui-xs-modal.xsstory-DJxKOKsQ.mjs","sources":["../../../projects/xs-ui/src/lib/components/base/modal/modal-side-panel-example.component.ts","../../../projects/xs-ui/src/lib/components/base/modal/modal-side-panel-example.component.html","../../../projects/xs-ui/src/lib/components/base/modal/modal-example.component.ts","../../../projects/xs-ui/src/lib/components/base/modal/modal-example.component.html","../../../projects/xs-ui/src/lib/components/base/modal/xs-modal.xsstory.ts"],"sourcesContent":["import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { XSButtonI } from '../button/xs-button.interface';\n\nexport interface ModalSidePanelData {\n artistName?: string;\n infoEn?: string;\n}\n\nexport interface ModalSidePanelResult {\n action: string;\n data: ModalSidePanelData;\n}\n\n/**\n * Contenido de ejemplo para el modo side-panel de xs-modal.\n * Formulario largo con textarea para validar scroll interno,\n * responsive (pantalla completa en móvil) y acciones de guardar/cancelar.\n */\n@Component({\n selector: 'modal-side-panel-example',\n host: { 'modal-side-panel-example-host': '' },\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule],\n templateUrl: './modal-side-panel-example.component.html',\n})\nexport class ModalSidePanelExampleComponent {\n modalData = input<ModalSidePanelData>();\n modalResult = output<ModalSidePanelResult | null>();\n\n artistName = signal('');\n infoEn = signal('');\n genre = signal('');\n savedResult = signal<string | null>(null);\n\n constructor() {\n const data = this.modalData();\n if (data?.artistName) this.artistName.set(data.artistName);\n if (data?.infoEn) this.infoEn.set(data.infoEn);\n }\n\n footerButtons(): XSButtonI[] {\n return [\n {\n id: 'sidePanelCancel',\n label: 'Cancelar',\n icon: { name: 'cancel' },\n action: 'cancel',\n },\n {\n id: 'sidePanelSave',\n label: 'Guardar',\n icon: { name: 'check' },\n action: 'save',\n },\n ];\n }\n\n submit(action: string): void {\n if (action === 'save') {\n const data: ModalSidePanelData = {\n artistName: this.artistName(),\n infoEn: this.infoEn(),\n };\n this.savedResult.set(\n `Guardado: ${data.artistName ?? '(sin nombre)'}`,\n );\n this.modalResult.emit({ action: 'save', data });\n } else {\n this.modalResult.emit(null);\n }\n }\n}\n","<div class=\"flex flex-col gap-4 p-1\">\n <p class=\"text-sm text-zinc-400\">\n Ejemplo de panel lateral con formulario extenso. El contenido\n debe desplazarse internamente cuando sea largo y el panel debe\n ocupar toda la pantalla en móvil.\n </p>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Nombre del artista\n <input\n class=\"rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n type=\"text\"\n placeholder=\"Ej. Aphex Twin\"\n [ngModel]=\"artistName()\"\n (ngModelChange)=\"artistName.set($event)\"\n />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Género\n <input\n class=\"rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n type=\"text\"\n placeholder=\"Ej. Electronic\"\n [ngModel]=\"genre()\"\n (ngModelChange)=\"genre.set($event)\"\n />\n </label>\n\n <label class=\"flex flex-col gap-1 text-sm text-zinc-300\">\n Biografía (infoEn)\n <textarea\n class=\"min-h-40 rounded-lg border border-zinc-700 bg-zinc-900 px-3 py-2 text-white outline-none focus:border-zinc-500\"\n rows=\"8\"\n placeholder=\"Pega aquí una biografía larga para probar el scroll y el teclado virtual…\"\n [ngModel]=\"infoEn()\"\n (ngModelChange)=\"infoEn.set($event)\"\n ></textarea>\n </label>\n\n @for (\n block of [\n 'Bloque de relleno 1: contenido largo para forzar el scroll interno del panel.',\n 'Bloque de relleno 2: verifica que la cabecera y las acciones sigan accesibles.',\n 'Bloque de relleno 3: en móvil no debe haber scroll horizontal ni contenido cortado.',\n 'Bloque de relleno 4: al cerrar se restaura el scroll de la página original.',\n ];\n track $index\n ) {\n <p class=\"rounded-lg bg-zinc-900 p-3 text-sm text-zinc-400\">\n {{ block }}\n </p>\n }\n\n @if (savedResult()) {\n <p class=\"rounded-lg bg-zinc-800 p-3 text-sm text-emerald-400\">\n {{ savedResult() }}\n </p>\n }\n</div>\n","import {\n Component,\n input,\n inject,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { XSModalService } from './xs-modal.service';\nimport { XSButtonComponent } from '../button/xs-button.component';\nimport { XSModalImageSearchComponent } from './types/image-search/xs-modal-image-search.component';\nimport { ModalSidePanelExampleComponent } from './modal-side-panel-example.component';\n\n@Component({\n selector: 'modal-example',\n host: { 'modal-example-host': '' },\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [XSButtonComponent],\n templateUrl: './modal-example.component.html',\n})\nexport class ModalExampleComponent {\n activeModal = input<\n | 'confirm'\n | 'info'\n | 'input'\n | 'claim'\n | 'imageSearch'\n | 'imageSearchSources'\n | 'sidePanel'\n | null\n >();\n\n private modalService = inject(XSModalService);\n\n openConfirm(): void {\n this.modalService\n .openConfirm({\n title: 'Confirmar eliminación',\n subTitle:\n '¿Estás seguro de que quieres eliminar este elemento?',\n labelAccept: 'Sí, eliminar',\n labelCancel: 'Cancelar',\n type: 'centered',\n })\n .subscribe((_result) => {});\n }\n\n openInfo(): void {\n this.modalService\n .openInfo({\n title: 'Información importante',\n info: '<p>Este es un mensaje <strong>importante</strong> que el usuario debe leer.</p><p>Contiene información crítica sobre el sistema.</p>',\n labelClose: 'Entendido',\n type: 'centered',\n })\n .subscribe(() => {});\n }\n\n openInput(): void {\n this.modalService\n .openInput({\n title: 'Ingrese su nombre',\n subTitle: 'Necesitamos su nombre para continuar',\n placeholder: 'Escriba su nombre aquí...',\n minLength: 2,\n maxLength: 50,\n labelAccept: 'Guardar',\n labelCancel: 'Cancelar',\n type: 'centered',\n })\n .subscribe((_result) => {});\n }\n\n openClaim(): void {\n this.modalService\n .openClaim({\n title: 'Reclamar elemento',\n subTitle: 'Esta acción requiere justificación',\n warningMsg:\n '¿Estás seguro de que quieres reclamar este elemento?',\n enterReason:\n 'Por favor explica el motivo de la reclamación',\n labelAccept: 'Reclamar',\n labelCancel: 'Cancelar',\n type: 'centered',\n })\n .subscribe((_result) => {});\n }\n\n openImageSearch(): void {\n this.modalService\n .open(XSModalImageSearchComponent, {\n type: 'bottom-sheet',\n title: 'Scraping de imágenes',\n subTitle: 'Busca y selecciona una imagen',\n data: {\n q: 'music',\n searchImages: async (query: string, limit: number) => {\n const images = Array.from(\n { length: limit },\n (_, i) =>\n `https://picsum.photos/seed/${encodeURIComponent(query)}${i}/300/300`,\n );\n return { images };\n },\n },\n })\n .subscribe((_result) => {});\n }\n\n openImageSearchWithSources(): void {\n const mockSoundcloudSearch = async (query: string) => {\n return {\n images: [\n `https://picsum.photos/seed/soundcloud-${encodeURIComponent(query)}/300/300`,\n ],\n };\n };\n\n const mockGoogleSearch = async (\n query: string,\n limit: number,\n start?: number,\n ) => {\n const totalResults = 15;\n const offset = start ?? 1;\n const images = Array.from(\n { length: Math.min(limit, totalResults - offset + 1) },\n (_, i) =>\n `https://picsum.photos/seed/${encodeURIComponent(query)}-g-${offset + i}/300/300`,\n );\n return { images, totalResults };\n };\n\n this.modalService\n .open(XSModalImageSearchComponent, {\n type: 'bottom-sheet',\n title: 'Scraping de imágenes',\n subTitle: 'Busca en Google Images y SoundCloud',\n data: {\n q: 'music',\n sources: [\n {\n id: 'google',\n label: 'Google Images',\n searchImages: mockGoogleSearch,\n },\n {\n id: 'soundcloud',\n label: 'SoundCloud',\n searchImages: mockSoundcloudSearch,\n },\n ],\n },\n })\n .subscribe((_result) => {});\n }\n\n openSidePanel(): void {\n this.modalService\n .open(ModalSidePanelExampleComponent, {\n type: 'side-panel',\n title: 'Editar artista',\n subTitle: 'Panel lateral (pantalla completa en móvil)',\n data: {\n artistName: 'Aphex Twin',\n infoEn:\n 'Richard David James, known as Aphex Twin, is a British electronic musician.',\n },\n })\n .subscribe((_result) => {});\n }\n}\n"," <div class=\"p-4\">\n <div class=\"grid grid-cols-1 gap-4\">\n @if (activeModal() === 'confirm' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Confirmación'\"\n (onClick)=\"openConfirm()\"\n ></xs-button>\n }\n @if (activeModal() === 'info' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Información'\"\n (onClick)=\"openInfo()\"\n ></xs-button>\n }\n @if (activeModal() === 'input' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Input'\"\n (onClick)=\"openInput()\"\n ></xs-button>\n }\n @if (activeModal() === 'claim' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Reclamo'\"\n (onClick)=\"openClaim()\"\n ></xs-button>\n }\n @if (\n activeModal() === 'imageSearch' || !activeModal()\n ) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Modal de Búsqueda de Imágenes'\"\n (onClick)=\"openImageSearch()\"\n class=\"mb-2\"\n ></xs-button>\n }\n @if (\n activeModal() === 'imageSearchSources' ||\n !activeModal()\n ) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir con Google + SoundCloud'\"\n (onClick)=\"openImageSearchWithSources()\"\n ></xs-button>\n }\n @if (activeModal() === 'sidePanel' || !activeModal()) {\n <xs-button\n variant=\"primary\"\n [label]=\"'Abrir Panel Lateral'\"\n (onClick)=\"openSidePanel()\"\n ></xs-button>\n }\n </div>\n </div>\n","import { StoryMeta } from '../../../story.types';\nimport { ModalExampleComponent } from './modal-example.component';\n\nexport const story: StoryMeta = {\n id: 'modal',\n name: 'Modal',\n type: 'component',\n subtype: 'Base',\n status: 'active',\n usage: ['admin', 'app', 'monitor'],\n updated: '2026-05-30T12:00:00.000Z',\n created: '2025-07-29T15:06:52.383Z',\n description:\n 'Componente Modal con diferentes tipos predefinidos y soporte multi-idioma automático con traducciones expandidas. ' +\n 'Contrato de footer reactivo: el componente inyectado puede exponer footerButtons() (XSButtonI[]) y submit(action). ' +\n 'El modal sincroniza los botones ante cada cambio (etiqueta, variante, disabled, loading) y enruta cancel→cierre, ' +\n 'accept/save/delete/load→submit(action) si existe (si no, cierra con {action}) y cualquier otra acción→cierre con null. ' +\n 'Aviso: una acción personalizada sin submit cierra el modal con null. ' +\n 'El footer es persistente por construcción (flex, fuera del área de scroll con safe-area inferior) y es opcional: ' +\n 'pasar buttons o exponer footerButtons() lo activa; omitirlos conserva el modal sin footer, sin cambios para los usos existentes.',\n component: ModalExampleComponent,\n selector: 'xs-modal',\n multilanguage: true,\n inputs: [\n {\n key: 'activeModal',\n label: 'Modal activo',\n type: 'select',\n options: [\n 'confirm',\n 'info',\n 'input',\n 'claim',\n 'imageSearch',\n 'imageSearchSources',\n 'sidePanel',\n ],\n default: null,\n },\n ],\n examples: [\n {\n name: 'Confirm Modal',\n description:\n 'Modal de confirmación para acciones del usuario',\n inputs: {\n activeModal: 'confirm',\n },\n },\n {\n name: 'Info Modal',\n description:\n 'Modal de información para mostrar mensajes al usuario',\n inputs: {\n activeModal: 'info',\n },\n },\n {\n name: 'Input Modal',\n description:\n 'Modal para solicitar entrada de datos del usuario',\n inputs: {\n activeModal: 'input',\n },\n },\n {\n name: 'Claim Modal',\n description:\n 'Modal de reclamación con campo de justificación',\n inputs: {\n activeModal: 'claim',\n },\n },\n {\n name: 'Image Search Modal',\n description:\n 'Modal de búsqueda de imágenes con input y resultados en grid',\n inputs: {\n activeModal: 'imageSearch',\n },\n },\n {\n name: 'Image Search con Google + SoundCloud',\n description:\n 'Modal de búsqueda de imágenes con múltiples fuentes: Google Images y SoundCloud',\n inputs: {\n activeModal: 'imageSearchSources',\n },\n },\n {\n name: 'Side Panel',\n description:\n 'Panel lateral derecho (pantalla completa en móvil) con formulario largo, textarea y scroll interno',\n inputs: {\n activeModal: 'sidePanel',\n },\n },\n ],\n coverage: {\n statements: 38,\n branches: 11,\n functions: 45,\n lines: 38,\n },\n};\n"],"names":[],"mappings":"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"}
@@ -7565,7 +7565,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
7565
7565
  ReactiveFormsModule,
7566
7566
  CountryFlagPipe,
7567
7567
  XSImageComponent,
7568
- XSIconComponent,
7569
7568
  XSFormlyLabelComponent,
7570
7569
  ], host: {
7571
7570
  '(document:click)': 'onDocumentClick($event)',
@@ -8070,14 +8069,14 @@ class XSModalComponent {
8070
8069
  return this.translateService.translate(key);
8071
8070
  }
8072
8071
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: XSModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8073
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: XSModalComponent, isStandalone: true, selector: "xs-modal", host: { attributes: { "xs-modal-host": "" }, listeners: { "document:keydown.escape": "onEscapeKey($event)" } }, viewQueries: [{ propertyName: "dynamicContentPlaceholder", first: true, predicate: ["dynamicContentPlaceholder"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<div\n class=\"modal-overlay\"\n role=\"dialog\"\n [attr.aria-modal]=\"true\"\n [attr.aria-label]=\"modalTitle() || translate('MODAL.DIALOG_LABEL')\"\n [ngClass]=\"{\n 'bottom-sheet': currentModalType() === 'bottom-sheet',\n centered: currentModalType() === 'centered',\n }\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n>\n <div\n class=\"relative p-4 bg-black shadow-lg max-h-[80vh] flex flex-col\"\n [ngClass]=\"{\n 'modal-bottom-sheet': currentModalType() === 'bottom-sheet',\n 'modal-centered': currentModalType() === 'centered',\n 'animate-slide-up':\n isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-slide-down':\n !isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-zoom-in':\n isVisible() && currentModalType() === 'centered',\n }\"\n role=\"document\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n (animationend)=\"onAnimationEnd()\"\n >\n @if (currentModalType() === 'bottom-sheet') {\n <div\n class=\"flex justify-center p-2\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <div class=\"w-10 h-1 bg-gray-300 rounded-full\"></div>\n </div>\n }\n @if (modalTitle()) {\n <div\n class=\"px-4 pb-2 text-center\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <h2 class=\"text-xl font-bold text-white\">\n {{ modalTitle() }}\n </h2>\n @if (modalSubTitle()) {\n <p class=\"text-sm text-gray-300\">\n {{ modalSubTitle() }}\n </p>\n }\n </div>\n }\n\n <div class=\"flex-1 px-2 overflow-y-auto\">\n <ng-template #dynamicContentPlaceholder></ng-template>\n </div>\n\n @if (buttons() && buttons().length > 0) {\n <div\n class=\"grid grid-cols-12 gap-2 py-4 mt-2 bg-black border-t border-zinc-800/30\"\n >\n @for (button of buttons(); track button.id ?? $index) {\n <xs-button\n [id]=\"\n button.id\n ? button.id\n : buttons().indexOf(button).toString()\n \"\n (click)=\"handleButtonAction(button.action!)\"\n [variant]=\"\n button.variant ? button.variant : 'primary'\n \"\n [layout]=\"button.layout ? button.layout : 'between'\"\n [isFull]=\"true\"\n [disabled]=\"button.disabled ?? false\"\n [icon]=\"button.icon\"\n [label]=\"button.label\"\n [ngClass]=\"getButtonClass(button)\"\n >\n </xs-button>\n }\n </div>\n }\n </div>\n</div>\n", styles: [".modal-overlay{position:fixed;inset:0;z-index:10000;display:flex;justify-content:center;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1));--tw-bg-opacity: .5}.modal-bottom-sheet{z-index:10001;width:100%;max-width:28rem;border-top-left-radius:1rem;border-top-right-radius:1rem}.modal-overlay.bottom-sheet{align-items:flex-end}.modal-centered{z-index:10001;margin:1rem;width:100%;max-width:32rem;border-radius:.75rem}.modal-overlay.centered{align-items:center}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slideDown{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes zoomIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.animate-zoom-in{animation:zoomIn .3s ease-out forwards}.animate-slide-up{animation:slideUp .3s ease-out forwards}.animate-slide-down{animation:slideDown .3s ease-in forwards}:host h2{text-transform:uppercase}:host-context(xs-button) button{text-transform:uppercase}.close-button{position:absolute;top:.75rem;right:.75rem;cursor:pointer;font-size:1.5rem;line-height:2rem;font-weight:700;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.close-button:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.modal-open{overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: XSButtonComponent, selector: "xs-button", inputs: ["id", "label", "type", "icon", "action", "customClass", "variant", "layout", "badgeCount", "isFull", "disabled", "loading", "title", "ariaLabel", "borderRadius", "isGrouped", "groupPosition"], outputs: ["isGroupedChange", "groupPositionChange", "click", "onClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8072
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: XSModalComponent, isStandalone: true, selector: "xs-modal", host: { attributes: { "xs-modal-host": "" }, listeners: { "document:keydown.escape": "onEscapeKey($event)" } }, viewQueries: [{ propertyName: "dynamicContentPlaceholder", first: true, predicate: ["dynamicContentPlaceholder"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<div\n class=\"modal-overlay\"\n role=\"dialog\"\n [attr.aria-modal]=\"true\"\n [attr.aria-label]=\"modalTitle() || translate('MODAL.DIALOG_LABEL')\"\n [ngClass]=\"{\n 'bottom-sheet': currentModalType() === 'bottom-sheet',\n centered: currentModalType() === 'centered',\n 'side-panel': currentModalType() === 'side-panel',\n }\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n>\n <div\n class=\"relative p-4 bg-black shadow-lg max-h-[80vh] flex flex-col\"\n [ngClass]=\"{\n 'modal-bottom-sheet': currentModalType() === 'bottom-sheet',\n 'modal-centered': currentModalType() === 'centered',\n 'modal-side-panel': currentModalType() === 'side-panel',\n 'animate-slide-up':\n isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-slide-down':\n !isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-zoom-in':\n isVisible() && currentModalType() === 'centered',\n 'animate-slide-in-right':\n isVisible() && currentModalType() === 'side-panel',\n 'animate-slide-out-right':\n !isVisible() && currentModalType() === 'side-panel',\n }\"\n role=\"document\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n (animationend)=\"onAnimationEnd()\"\n >\n @if (currentModalType() === 'bottom-sheet') {\n <div\n class=\"flex justify-center p-2\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <div class=\"w-10 h-1 bg-gray-300 rounded-full\"></div>\n </div>\n }\n @if (modalTitle()) {\n <div\n class=\"px-4 pb-2 text-center\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <h2 class=\"text-xl font-bold text-white\">\n {{ modalTitle() }}\n </h2>\n @if (modalSubTitle()) {\n <p class=\"text-sm text-gray-300\">\n {{ modalSubTitle() }}\n </p>\n }\n </div>\n }\n\n <div class=\"flex-1 px-2 overflow-y-auto overflow-x-hidden\">\n <ng-template #dynamicContentPlaceholder></ng-template>\n </div>\n\n @if (buttons() && buttons().length > 0) {\n <div\n class=\"modal-footer grid grid-cols-12 gap-2 pt-4 mt-2 bg-black border-t border-zinc-800/30\"\n >\n @for (button of buttons(); track button.id ?? $index) {\n <xs-button\n [id]=\"\n button.id\n ? button.id\n : buttons().indexOf(button).toString()\n \"\n (click)=\"handleButtonAction(button.action!)\"\n [variant]=\"\n button.variant ? button.variant : 'primary'\n \"\n [layout]=\"button.layout ? button.layout : 'between'\"\n [isFull]=\"true\"\n [disabled]=\"button.disabled ?? false\"\n [loading]=\"button.loading ?? false\"\n [icon]=\"button.icon\"\n [label]=\"button.label\"\n [ngClass]=\"getButtonClass(button)\"\n >\n </xs-button>\n }\n </div>\n }\n </div>\n</div>\n", styles: [".modal-overlay{position:fixed;inset:0;z-index:10000;display:flex;justify-content:center;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1));--tw-bg-opacity: .5}.modal-bottom-sheet{z-index:10001;width:100%;max-width:28rem;border-top-left-radius:1rem;border-top-right-radius:1rem}.modal-overlay.bottom-sheet{align-items:flex-end}.modal-centered{z-index:10001;margin:1rem;width:100%;max-width:32rem;border-radius:.75rem}.modal-overlay.centered{align-items:center}.modal-footer{padding-bottom:max(1rem,env(safe-area-inset-bottom,0px))}.modal-side-panel{z-index:10001;margin:0;height:100vh;max-height:100vh;width:100%;border-radius:0;border-left-width:1px;--tw-border-opacity: 1;border-color:rgb(39 39 42 / var(--tw-border-opacity, 1))}@media(min-width:640px){.modal-side-panel{max-width:36rem}}@media(min-width:1024px){.modal-side-panel{max-width:42rem}}.modal-side-panel{height:100dvh;max-height:100dvh}.modal-overlay.side-panel{align-items:stretch;justify-content:flex-end;padding:0}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slideDown{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes zoomIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes slideOutRight{0%{transform:translate(0)}to{transform:translate(100%)}}.animate-slide-in-right{animation:slideInRight .3s ease-out forwards}.animate-slide-out-right{animation:slideOutRight .3s ease-in forwards}.animate-zoom-in{animation:zoomIn .3s ease-out forwards}.animate-slide-up{animation:slideUp .3s ease-out forwards}.animate-slide-down{animation:slideDown .3s ease-in forwards}:host h2{text-transform:uppercase}:host-context(xs-button) button{text-transform:uppercase}.close-button{position:absolute;top:.75rem;right:.75rem;cursor:pointer;font-size:1.5rem;line-height:2rem;font-weight:700;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.close-button:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.modal-open{overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: XSButtonComponent, selector: "xs-button", inputs: ["id", "label", "type", "icon", "action", "customClass", "variant", "layout", "badgeCount", "isFull", "disabled", "loading", "title", "ariaLabel", "borderRadius", "isGrouped", "groupPosition"], outputs: ["isGroupedChange", "groupPositionChange", "click", "onClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8074
8073
  }
8075
8074
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: XSModalComponent, decorators: [{
8076
8075
  type: Component,
8077
8076
  args: [{ selector: 'xs-modal', imports: [CommonModule, XSButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
8078
8077
  'xs-modal-host': '',
8079
8078
  '(document:keydown.escape)': 'onEscapeKey($event)',
8080
- }, template: "<div\n class=\"modal-overlay\"\n role=\"dialog\"\n [attr.aria-modal]=\"true\"\n [attr.aria-label]=\"modalTitle() || translate('MODAL.DIALOG_LABEL')\"\n [ngClass]=\"{\n 'bottom-sheet': currentModalType() === 'bottom-sheet',\n centered: currentModalType() === 'centered',\n }\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n>\n <div\n class=\"relative p-4 bg-black shadow-lg max-h-[80vh] flex flex-col\"\n [ngClass]=\"{\n 'modal-bottom-sheet': currentModalType() === 'bottom-sheet',\n 'modal-centered': currentModalType() === 'centered',\n 'animate-slide-up':\n isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-slide-down':\n !isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-zoom-in':\n isVisible() && currentModalType() === 'centered',\n }\"\n role=\"document\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n (animationend)=\"onAnimationEnd()\"\n >\n @if (currentModalType() === 'bottom-sheet') {\n <div\n class=\"flex justify-center p-2\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <div class=\"w-10 h-1 bg-gray-300 rounded-full\"></div>\n </div>\n }\n @if (modalTitle()) {\n <div\n class=\"px-4 pb-2 text-center\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <h2 class=\"text-xl font-bold text-white\">\n {{ modalTitle() }}\n </h2>\n @if (modalSubTitle()) {\n <p class=\"text-sm text-gray-300\">\n {{ modalSubTitle() }}\n </p>\n }\n </div>\n }\n\n <div class=\"flex-1 px-2 overflow-y-auto\">\n <ng-template #dynamicContentPlaceholder></ng-template>\n </div>\n\n @if (buttons() && buttons().length > 0) {\n <div\n class=\"grid grid-cols-12 gap-2 py-4 mt-2 bg-black border-t border-zinc-800/30\"\n >\n @for (button of buttons(); track button.id ?? $index) {\n <xs-button\n [id]=\"\n button.id\n ? button.id\n : buttons().indexOf(button).toString()\n \"\n (click)=\"handleButtonAction(button.action!)\"\n [variant]=\"\n button.variant ? button.variant : 'primary'\n \"\n [layout]=\"button.layout ? button.layout : 'between'\"\n [isFull]=\"true\"\n [disabled]=\"button.disabled ?? false\"\n [icon]=\"button.icon\"\n [label]=\"button.label\"\n [ngClass]=\"getButtonClass(button)\"\n >\n </xs-button>\n }\n </div>\n }\n </div>\n</div>\n", styles: [".modal-overlay{position:fixed;inset:0;z-index:10000;display:flex;justify-content:center;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1));--tw-bg-opacity: .5}.modal-bottom-sheet{z-index:10001;width:100%;max-width:28rem;border-top-left-radius:1rem;border-top-right-radius:1rem}.modal-overlay.bottom-sheet{align-items:flex-end}.modal-centered{z-index:10001;margin:1rem;width:100%;max-width:32rem;border-radius:.75rem}.modal-overlay.centered{align-items:center}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slideDown{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes zoomIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.animate-zoom-in{animation:zoomIn .3s ease-out forwards}.animate-slide-up{animation:slideUp .3s ease-out forwards}.animate-slide-down{animation:slideDown .3s ease-in forwards}:host h2{text-transform:uppercase}:host-context(xs-button) button{text-transform:uppercase}.close-button{position:absolute;top:.75rem;right:.75rem;cursor:pointer;font-size:1.5rem;line-height:2rem;font-weight:700;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.close-button:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.modal-open{overflow:hidden}\n"] }]
8079
+ }, template: "<div\n class=\"modal-overlay\"\n role=\"dialog\"\n [attr.aria-modal]=\"true\"\n [attr.aria-label]=\"modalTitle() || translate('MODAL.DIALOG_LABEL')\"\n [ngClass]=\"{\n 'bottom-sheet': currentModalType() === 'bottom-sheet',\n centered: currentModalType() === 'centered',\n 'side-panel': currentModalType() === 'side-panel',\n }\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n>\n <div\n class=\"relative p-4 bg-black shadow-lg max-h-[80vh] flex flex-col\"\n [ngClass]=\"{\n 'modal-bottom-sheet': currentModalType() === 'bottom-sheet',\n 'modal-centered': currentModalType() === 'centered',\n 'modal-side-panel': currentModalType() === 'side-panel',\n 'animate-slide-up':\n isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-slide-down':\n !isVisible() && currentModalType() === 'bottom-sheet',\n 'animate-zoom-in':\n isVisible() && currentModalType() === 'centered',\n 'animate-slide-in-right':\n isVisible() && currentModalType() === 'side-panel',\n 'animate-slide-out-right':\n !isVisible() && currentModalType() === 'side-panel',\n }\"\n role=\"document\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n (animationend)=\"onAnimationEnd()\"\n >\n @if (currentModalType() === 'bottom-sheet') {\n <div\n class=\"flex justify-center p-2\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <div class=\"w-10 h-1 bg-gray-300 rounded-full\"></div>\n </div>\n }\n @if (modalTitle()) {\n <div\n class=\"px-4 pb-2 text-center\"\n (mousedown)=\"$event.stopPropagation()\"\n (mouseup)=\"$event.stopPropagation()\"\n >\n <h2 class=\"text-xl font-bold text-white\">\n {{ modalTitle() }}\n </h2>\n @if (modalSubTitle()) {\n <p class=\"text-sm text-gray-300\">\n {{ modalSubTitle() }}\n </p>\n }\n </div>\n }\n\n <div class=\"flex-1 px-2 overflow-y-auto overflow-x-hidden\">\n <ng-template #dynamicContentPlaceholder></ng-template>\n </div>\n\n @if (buttons() && buttons().length > 0) {\n <div\n class=\"modal-footer grid grid-cols-12 gap-2 pt-4 mt-2 bg-black border-t border-zinc-800/30\"\n >\n @for (button of buttons(); track button.id ?? $index) {\n <xs-button\n [id]=\"\n button.id\n ? button.id\n : buttons().indexOf(button).toString()\n \"\n (click)=\"handleButtonAction(button.action!)\"\n [variant]=\"\n button.variant ? button.variant : 'primary'\n \"\n [layout]=\"button.layout ? button.layout : 'between'\"\n [isFull]=\"true\"\n [disabled]=\"button.disabled ?? false\"\n [loading]=\"button.loading ?? false\"\n [icon]=\"button.icon\"\n [label]=\"button.label\"\n [ngClass]=\"getButtonClass(button)\"\n >\n </xs-button>\n }\n </div>\n }\n </div>\n</div>\n", styles: [".modal-overlay{position:fixed;inset:0;z-index:10000;display:flex;justify-content:center;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1));--tw-bg-opacity: .5}.modal-bottom-sheet{z-index:10001;width:100%;max-width:28rem;border-top-left-radius:1rem;border-top-right-radius:1rem}.modal-overlay.bottom-sheet{align-items:flex-end}.modal-centered{z-index:10001;margin:1rem;width:100%;max-width:32rem;border-radius:.75rem}.modal-overlay.centered{align-items:center}.modal-footer{padding-bottom:max(1rem,env(safe-area-inset-bottom,0px))}.modal-side-panel{z-index:10001;margin:0;height:100vh;max-height:100vh;width:100%;border-radius:0;border-left-width:1px;--tw-border-opacity: 1;border-color:rgb(39 39 42 / var(--tw-border-opacity, 1))}@media(min-width:640px){.modal-side-panel{max-width:36rem}}@media(min-width:1024px){.modal-side-panel{max-width:42rem}}.modal-side-panel{height:100dvh;max-height:100dvh}.modal-overlay.side-panel{align-items:stretch;justify-content:flex-end;padding:0}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slideDown{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes zoomIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes slideOutRight{0%{transform:translate(0)}to{transform:translate(100%)}}.animate-slide-in-right{animation:slideInRight .3s ease-out forwards}.animate-slide-out-right{animation:slideOutRight .3s ease-in forwards}.animate-zoom-in{animation:zoomIn .3s ease-out forwards}.animate-slide-up{animation:slideUp .3s ease-out forwards}.animate-slide-down{animation:slideDown .3s ease-in forwards}:host h2{text-transform:uppercase}:host-context(xs-button) button{text-transform:uppercase}.close-button{position:absolute;top:.75rem;right:.75rem;cursor:pointer;font-size:1.5rem;line-height:2rem;font-weight:700;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.close-button:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.modal-open{overflow:hidden}\n"] }]
8081
8080
  }], ctorParameters: () => [], propDecorators: { dynamicContentPlaceholder: [{ type: i0.ViewChild, args: ['dynamicContentPlaceholder', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
8082
8081
 
8083
8082
  class XSModalConfirmComponent {
@@ -14457,7 +14456,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
14457
14456
  XSButtonComponent,
14458
14457
  XSIconComponent,
14459
14458
  XSFormlyComponent,
14460
- XSSpinnerComponent,
14461
14459
  XSEmptyStateComponent,
14462
14460
  XSTabsComponent,
14463
14461
  XSImportMediaCardComponent,
@@ -16409,7 +16407,7 @@ class XSSelectComponent {
16409
16407
  }
16410
16408
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: XSSelectComponent, decorators: [{
16411
16409
  type: Component,
16412
- args: [{ selector: 'xs-select', standalone: true, imports: [CommonModule, XSIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
16410
+ args: [{ selector: 'xs-select', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, host: {
16413
16411
  '(document:click)': 'onDocumentClick($event)',
16414
16412
  }, template: "<div class=\"relative\">\n <input\n type=\"text\"\n [value]=\"displayText()\"\n readonly\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n [class]=\"inputClass()\"\n [class.text-zinc-400]=\"!selectedOption()\"\n [attr.placeholder]=\"placeholder()\"\n role=\"combobox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n tabindex=\"0\"\n />\n\n <div\n class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center\"\n [class.pr-4]=\"size() === 'md'\"\n [class.pr-3]=\"size() === 'sm'\"\n >\n <svg\n class=\"text-white transition-transform duration-200\"\n [class.w-4.h-4]=\"size() === 'md'\"\n [class.w-3.h-3]=\"size() === 'sm'\"\n [class.rotate-180]=\"isOpen()\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n\n @if (isOpen()) {\n <div\n class=\"fixed z-[10002] rounded-xl shadow-lg min-h-[100px] bg-black overflow-hidden\"\n [style.top.px]=\"dropdownTop\"\n [style.left.px]=\"dropdownLeft\"\n [style.width.px]=\"dropdownWidth\"\n >\n <div class=\"bg-zinc-900/50 rounded-xl\">\n <ul class=\"max-h-48 overflow-y-auto overscroll-contain\" role=\"listbox\">\n @for (option of options(); track option.id) {\n <li\n role=\"option\"\n [attr.aria-selected]=\"option.id === value()\"\n (click)=\"selectOption(option)\"\n (keydown)=\"onOptionKeydown($event, option)\"\n tabindex=\"0\"\n [class]=\"optionClass()\"\n [class.bg-white/5]=\"option.id === value()\"\n >\n <span class=\"flex-1 min-w-0 truncate text-white\">{{ option.name }}</span>\n @if (option.id === value()) {\n <svg\n class=\"text-white shrink-0\"\n [class.w-4.h-4]=\"size() === 'md'\"\n [class.w-3.h-3]=\"size() === 'sm'\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n }\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>\n" }]
16415
16413
  }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
@@ -23142,7 +23140,7 @@ const LIB_STORIES = [
23142
23140
  () => import('./xsmusices-ui-xs-empty-state.xsstory-CCOJ4b9_.mjs').then((m) => m.story),
23143
23141
  () => import('./xsmusices-ui-xs-icon.xsstory-asFaHy5H.mjs').then((m) => m.story),
23144
23142
  () => import('./xsmusices-ui-xs-image.xsstory-BaeUg3Zp.mjs').then((m) => m.story),
23145
- () => import('./xsmusices-ui-xs-modal.xsstory-B96CYHF0.mjs').then((m) => m.story),
23143
+ () => import('./xsmusices-ui-xs-modal.xsstory-DJxKOKsQ.mjs').then((m) => m.story),
23146
23144
  () => import('./xsmusices-ui-xs-navbar.xsstory-BDW0nLAH.mjs').then((m) => m.story),
23147
23145
  () => import('./xsmusices-ui-xs-navbar-sidebar.xsstory-DHmPSARx.mjs').then((m) => m.story),
23148
23146
  () => import('./xsmusices-ui-stats-card.xsstory-PWTUDysc.mjs').then((m) => m.story),