@ajuarezso/sui-design-system 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/INDEX.md +75 -80
- package/docs/sui-accordion/README.md +171 -0
- package/docs/sui-activity-feed/README.md +155 -0
- package/docs/sui-activity-filter/README.md +100 -0
- package/docs/sui-alert/README.md +13 -0
- package/docs/sui-badge/README.md +17 -0
- package/docs/sui-banner/README.md +190 -0
- package/docs/sui-breadcrumb-item/README.md +96 -0
- package/docs/sui-breadcrumbs/README.md +122 -0
- package/docs/sui-button/README.md +36 -0
- package/docs/sui-card/README.md +168 -0
- package/docs/sui-chart-legend/README.md +140 -0
- package/docs/sui-chart-legend-dot/README.md +118 -0
- package/docs/sui-checkbox-card/README.md +150 -0
- package/docs/sui-command-menu-footer/README.md +100 -0
- package/docs/sui-command-menu-item/README.md +140 -0
- package/docs/sui-command-menu-search/README.md +105 -0
- package/docs/sui-compact-select/README.md +130 -0
- package/docs/sui-compact-select-for-input/README.md +120 -0
- package/docs/sui-content-card/README.md +147 -0
- package/docs/sui-content-label/README.md +148 -0
- package/docs/sui-counter-input/README.md +148 -0
- package/docs/sui-date-selector/README.md +98 -0
- package/docs/sui-datepicker/README.md +197 -0
- package/docs/sui-day-cell/README.md +118 -0
- package/docs/sui-day-label/README.md +81 -0
- package/docs/sui-drawer-footer/README.md +127 -0
- package/docs/sui-drawer-header/README.md +140 -0
- package/docs/sui-dropdown-caption/README.md +92 -0
- package/docs/sui-dropdown-item/README.md +163 -0
- package/docs/sui-dropdown-menu/README.md +196 -0
- package/docs/sui-dropdown-search/README.md +113 -0
- package/docs/sui-empty-state/README.md +145 -0
- package/docs/sui-file-upload/README.md +154 -0
- package/docs/sui-file-upload-card/README.md +129 -0
- package/docs/sui-filter/README.md +181 -0
- package/docs/sui-href/README.md +85 -0
- package/docs/sui-image-upload/README.md +200 -0
- package/docs/sui-image-upload-profile/README.md +145 -0
- package/docs/sui-img-retry/README.md +107 -0
- package/docs/sui-inline-input/README.md +104 -0
- package/docs/sui-inline-select/README.md +101 -0
- package/docs/sui-input/README.md +247 -0
- package/docs/sui-integration-switch/README.md +142 -0
- package/docs/sui-modal/README.md +238 -0
- package/docs/sui-modal-footer/README.md +178 -0
- package/docs/sui-modal-header/README.md +115 -0
- package/docs/sui-modal-sheet/README.md +170 -0
- package/docs/sui-notification/README.md +179 -0
- package/docs/sui-notification-footer/README.md +98 -0
- package/docs/sui-notification-tab-menu/README.md +112 -0
- package/docs/sui-otp-input/README.md +116 -0
- package/docs/sui-page-header/README.md +123 -0
- package/docs/sui-pagination/README.md +146 -0
- package/docs/sui-password-strength/README.md +99 -0
- package/docs/sui-period-range/README.md +93 -0
- package/docs/sui-popover/README.md +178 -0
- package/docs/sui-product-card/README.md +173 -0
- package/docs/sui-radio-card/README.md +160 -0
- package/docs/sui-rating/README.md +7 -6
- package/docs/sui-rich-editor/README.md +114 -0
- package/docs/sui-segmented-control/README.md +3 -0
- package/docs/sui-select/README.md +183 -0
- package/docs/sui-sidebar/README.md +217 -0
- package/docs/sui-stepper/README.md +166 -0
- package/docs/sui-store-card/README.md +147 -0
- package/docs/sui-switch-card/README.md +149 -0
- package/docs/sui-switch-label/README.md +3 -0
- package/docs/sui-tab-chips/README.md +188 -0
- package/docs/sui-tab-menu-horizontal/README.md +142 -0
- package/docs/sui-tab-menu-vertical/README.md +136 -0
- package/docs/sui-table/README.md +264 -0
- package/docs/sui-tag-input/README.md +123 -0
- package/docs/sui-textarea/README.md +160 -0
- package/docs/sui-time-picker/README.md +240 -0
- package/docs/sui-toast/README.md +190 -0
- package/docs/sui-tooltip/README.md +201 -0
- package/docs/sui-topbar/README.md +153 -0
- package/docs/sui-topbar-button/README.md +95 -0
- package/docs/sui-topbar-item/README.md +97 -0
- package/docs/sui-topbar-user/README.md +83 -0
- package/fesm2022/ajuarezso-sui-design-system.mjs +291 -246
- package/fesm2022/ajuarezso-sui-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ajuarezso-sui-design-system.d.ts +208 -50
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-modal
|
|
3
|
+
selector: sui-modal
|
|
4
|
+
resumen: Diálogo que se abre encima de la pantalla y pide terminar o cerrar algo antes de seguir
|
|
5
|
+
alternativas: [sui-modal-sheet (hoja que sube desde abajo y deja ver lo de atrás, p. ej. un mapa), sui-alert (aviso en la misma pantalla, sin interrumpir), sui-toast (confirmación breve de una acción), sui-popover (contenido chico anclado a un botón), sui-drawer-header (panel lateral)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-modal
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Modal/Modal](https://sui-design-system.web.app/?path=/docs/organisms-modal-modal--docs)
|
|
11
|
+
|
|
12
|
+
## Cuándo usarlo / cuándo no
|
|
13
|
+
|
|
14
|
+
**Úsalo** cuando la persona tiene que **hacer o decidir algo antes de volver** a la pantalla:
|
|
15
|
+
llenar un formulario corto (crear un descuento, editar sus datos), confirmar algo que no se
|
|
16
|
+
deshace (eliminar, cancelar un pedido), ver un detalle que no merece pantalla propia (un
|
|
17
|
+
comprobante, el detalle de un pedido).
|
|
18
|
+
|
|
19
|
+
**No lo uses** para:
|
|
20
|
+
|
|
21
|
+
- Contenido que tiene que convivir con lo de atrás (un mapa que se sigue moviendo, el
|
|
22
|
+
seguimiento de un pedido): eso es `sui-modal-sheet`.
|
|
23
|
+
- Avisar algo sin pedir respuesta: `sui-alert` en la pantalla, o `sui-toast` si es la
|
|
24
|
+
confirmación de un clic.
|
|
25
|
+
- Un flujo de varios pasos o un formulario largo: merece su propia ruta.
|
|
26
|
+
- Modal sobre modal. El componente lo soporta (bloqueo de scroll y barra inferior se cuentan
|
|
27
|
+
por instancia), pero dos capas de backdrop confunden: cierra el primero o cambia su contenido.
|
|
28
|
+
|
|
29
|
+
Regla corta: **hay que responder → modal · se mira junto al fondo → sheet · solo se avisa → alert/toast.**
|
|
30
|
+
|
|
31
|
+
## Qué variante usar
|
|
32
|
+
|
|
33
|
+
El modal tiene tres decisiones: **cómo se presenta en mobile** (`fullscreen`), **qué tan ancho
|
|
34
|
+
es en desktop** (`size`) y **qué cabecera lleva** (`iconVariant` / `headerIcon` /
|
|
35
|
+
`statusAlignment`). Las tres se eligen por separado.
|
|
36
|
+
|
|
37
|
+
> **La guía del DS dibuja la anatomía pero no dice cuándo usar cada cosa.** El Figma del DS
|
|
38
|
+
> trae el header (nodo 466:4778, tipos Basic / Left Icon / Error / Warning / Success / Info en
|
|
39
|
+
> tamaños Small 56 y Medium 80), el footer (472:566), los Status Modals (480:1372) y el overlay
|
|
40
|
+
> (480:2474). No trae do/don't. Lo marcado **canon** sale de `master-pedidos/CLAUDE.md` y
|
|
41
|
+
> `docs/CANON-SUI.md`; lo marcado **propuesta** falta aprobar.
|
|
42
|
+
>
|
|
43
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app`: 128 modales, sin contar prototipos ni
|
|
44
|
+
> vitrinas (`master-preview`, `master-prototipos`, `mockup-sheets-*`, `dx/*`, `master-rutina`,
|
|
45
|
+
> `master-medico`, `dev-index`), las copias locales de `components/sui/` ni los `.spec.ts`.
|
|
46
|
+
|
|
47
|
+
### Lo que sí dice la guía del DS
|
|
48
|
+
|
|
49
|
+
- **Contenedor**: fondo blanco, borde `stroke-soft-200`, radio 20 y sombra media. El overlay es
|
|
50
|
+
gris al 24 %; el **blur de 10 px es extensión de SUI** (se quita con `[backdropBlur]="false"`).
|
|
51
|
+
- **Anchos**: `small` 400 · `medium` 440 · `large` 560 · `xlarge` 720 px.
|
|
52
|
+
- **Header Small (56)** = solo título; **Medium (80)** = título + descripción. El modal elige
|
|
53
|
+
solo: con `description` va Medium, sin ella Small.
|
|
54
|
+
- **Body** con 20 px de padding y 20 px entre bloques.
|
|
55
|
+
- **Status Modal**: icono en un recuadro y texto centrados (vertical) o en fila (horizontal),
|
|
56
|
+
sin header. En SUI es `statusAlignment`.
|
|
57
|
+
|
|
58
|
+
### `fullscreen` — cómo se presenta en mobile (lo que más cambió)
|
|
59
|
+
|
|
60
|
+
`fullscreen` **solo actúa en pantallas de 768 px o menos**; en desktop el modal sigue centrado
|
|
61
|
+
con su `size`. Desde el paquete **0.21.0 el default es `'auto'`**: dentro de la app nativa
|
|
62
|
+
(clase `platform-ios` / `platform-android` en `<html>`) va a pantalla completa, y en el
|
|
63
|
+
navegador del teléfono queda centrado.
|
|
64
|
+
|
|
65
|
+
| valor | qué hace | cuándo | fuente |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| sin poner (`'auto'`) | pantalla completa en la app, centrado en la web móvil | todo modal de contenido: formularios, detalles, selección | propuesta (reemplaza el `[fullscreen]="true"` a mano) |
|
|
68
|
+
| `[fullscreen]="true"` | pantalla completa también en la web móvil | lo que hoy hace toda la app por regla; con `'auto'` solo cambia la web móvil | canon (CLAUDE.md, 2026-07-26), **a revisar** con el `'auto'` |
|
|
69
|
+
| `[fullscreen]="false"` | siempre centrado | **confirmación de una línea con dos botones** («¿Cancelar pedido?»): ocupar toda la pantalla para un sí/no la hace parecer más grande de lo que es | canon (CLAUDE.md y CANON-SUI §6.5) |
|
|
70
|
+
|
|
71
|
+
Hoy: 95 con `true`, 32 sin el atributo, 1 con `false` (`pages/driver/driver-order-detail/driver-order-detail.html:986`).
|
|
72
|
+
|
|
73
|
+
**Usos de hoy que rompen el canon** (no tienen `[fullscreen]="false"`, así que con el `'auto'`
|
|
74
|
+
de 0.21.0 se abren **a pantalla completa** dentro de la app):
|
|
75
|
+
|
|
76
|
+
- `components/confirm-dialog/confirm-dialog.html:1` — el diálogo genérico que abre
|
|
77
|
+
`SuiConfirmService.confirm()`. Arreglarlo aquí arregla todas sus llamadas.
|
|
78
|
+
- Sí/no sin campos: `pages/driver/driver-order-detail/driver-order-detail.html:771` («¿Recibiste
|
|
79
|
+
el efectivo?») y `:857` («¿Liberar este pedido?»), `pages/central/central-inicio/central-inicio.html:140`,
|
|
80
|
+
`pages/central/central-locales/central-locales.html:76`, `pages/super-admin/master-gastos/master-gastos.html:279`,
|
|
81
|
+
`pages/super-admin/master-drivers/master-drivers.html:619`, `pages/super-admin/master-orders/master-orders.html:766`,
|
|
82
|
+
`pages/mi-negocio/mi-negocio.html:1248`, `pages/customer/restaurant-menu/restaurant-menu.html:487`,
|
|
83
|
+
`pages/customer/order-tracking/order-tracking.html:756`.
|
|
84
|
+
- Las confirmaciones **con un campo de motivo** (`sui-textarea`) sí van mejor a pantalla
|
|
85
|
+
completa por el teclado: `pages/customer/order-tracking/order-tracking.html:774`,
|
|
86
|
+
`pages/central/central-gastos/central-gastos.html:128`, los «Rechazar…» y «Suspender» de
|
|
87
|
+
`pages/super-admin/super-admin.html`. Ahí el `'auto'` acierta.
|
|
88
|
+
|
|
89
|
+
### `size` — el ancho en desktop
|
|
90
|
+
|
|
91
|
+
En mobile **no se usa**: a 768 px o menos el contenedor toma `max-width: 100%` (centrado) o la
|
|
92
|
+
pantalla entera (fullscreen). Elegirlo es una decisión de desktop.
|
|
93
|
+
|
|
94
|
+
| size | cuándo | ejemplo real de la app | fuente |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `small` (400) | confirmación o aviso: título, una o dos frases y los botones | `pages/customer/order-tracking/order-tracking.html:756` | propuesta |
|
|
97
|
+
| `medium` (440, default) | formulario corto (hasta ~5 campos) o un detalle simple | `pages/discounts/discounts.html:122` («Crear descuento») | propuesta |
|
|
98
|
+
| `large` (560) | detalle con tabla o imagen: comprobantes, el detalle de un pedido | `pages/orders/orders.html:146` · `pages/super-admin/master-drivers/master-drivers.html:685` | propuesta |
|
|
99
|
+
| `xlarge` (720) | mapa, formularios de dos columnas, listas largas de opciones | `components/address-picker-modal/address-picker-modal.html:1` · `pages/customer/checkout/checkout.html:1` | propuesta |
|
|
100
|
+
|
|
101
|
+
Hoy: 45 `small`, 63 `medium` (50 explícitos + 13 por default), 5 `large`, 14 `xlarge` y 1 que cambia según el caso.
|
|
102
|
+
|
|
103
|
+
### Cabecera — `iconVariant`, `headerIcon`, `statusAlignment`
|
|
104
|
+
|
|
105
|
+
| input | qué dibuja | cuándo | fuente |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| ninguno (Basic) | solo título (+ descripción) y la X | el caso normal: un formulario o un detalle | propuesta |
|
|
108
|
+
| `iconVariant` | círculo de color con el ícono del estado (`info`, `success`, `warning`, `error`) | el modal **es** un aviso de ese tipo: «Eliminar usuario» (`error`), «Estás a punto de cambiar de local» (`warning`) | propuesta |
|
|
109
|
+
| `headerIcon` | círculo gris con un ícono propio (Remix) | identifica **qué se está editando**: `edit-line`, `user-add-line`, `time-line`. Gana sobre `iconVariant` | propuesta |
|
|
110
|
+
| `statusAlignment` | quita el header y pone ícono + texto dentro del body (`vertical` centrado, `horizontal` en fila) | pantallas de resultado («Pedido enviado») o confirmaciones con más peso visual. Requiere `iconVariant` o `headerIcon`. 0 usos hoy | según el Figma del DS (Status Modals) |
|
|
111
|
+
|
|
112
|
+
**Usos de hoy que rompen el copy canon** (CANON-SUI: títulos en sentence case):
|
|
113
|
+
`pages/super-admin/super-admin.html:353` «Suspender Negocio», `:471` «Evidencia de Pago»,
|
|
114
|
+
`:487` «Rechazar Solicitud», `:557` «Rechazar Promoción».
|
|
115
|
+
|
|
116
|
+
### Cerrar
|
|
117
|
+
|
|
118
|
+
| input | default | cuándo cambiarlo | fuente |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| `closeOnBackdrop` | `true` | `false` si el modal tiene un formulario a medio llenar o una operación en curso (`[closeOnBackdrop]="!guardando()"`): un toque fuera no debe tirar lo escrito. Hoy 42 en `false` y 5 dinámicos | propuesta |
|
|
121
|
+
| `showClose` | `true` | `false` solo cuando la persona **tiene** que elegir un botón (actualización obligatoria, oferta de pedido al repartidor). Hoy 8 | propuesta |
|
|
122
|
+
| `closeOnEscape` | `true` | casi nunca: es la salida por teclado en desktop | propuesta |
|
|
123
|
+
|
|
124
|
+
### `fullBody` — contenido a sangre
|
|
125
|
+
|
|
126
|
+
Quita los 20 px del body para mapas o imágenes de borde a borde. Hoy 12 usos
|
|
127
|
+
(`components/map-picker-modal/map-picker-modal.html:1`, el comprobante de
|
|
128
|
+
`pages/driver/driver-order-detail/driver-order-detail.html:1091`). Con `fullBody` el espaciado
|
|
129
|
+
interno lo pone quien proyecta. Canon (CLAUDE.md, 2026-07-21).
|
|
130
|
+
|
|
131
|
+
## Tamaño en mobile y en web
|
|
132
|
+
|
|
133
|
+
| | mobile app (≤ 768 px) | web móvil (≤ 768 px) | web desktop | fuente |
|
|
134
|
+
|---|---|---|---|---|
|
|
135
|
+
| presentación | pantalla completa flotante (8 px de margen) | centrado, ancho completo menos 16 px | centrado | componente |
|
|
136
|
+
| `size` | no aplica | no aplica | el de la tabla de arriba | componente |
|
|
137
|
+
| alto máximo | toda la pantalla menos safe-areas | **la mitad de la pantalla** (`50dvh − 16px`); el body scrollea | `100dvh − 32px` | componente |
|
|
138
|
+
| esquinas | iOS 36 arriba / 54 abajo (concéntrico con el display) · Android 20 | 20 | 20 | canon (CLAUDE.md, 2026-07-22) |
|
|
139
|
+
| botones del footer | `large` (48) | `large` (48) | `large` (48) | canon (ver `sui-modal-footer`) |
|
|
140
|
+
|
|
141
|
+
El modal **no tiene un size para mobile**: la decisión de mobile es `fullscreen`.
|
|
142
|
+
|
|
143
|
+
## API
|
|
144
|
+
|
|
145
|
+
<!-- api:inicio -->
|
|
146
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
147
|
+
|
|
148
|
+
| input | tipo | default |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `open` · model, `[(open)]` | `boolean` | `false` |
|
|
151
|
+
| `size` | `'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` |
|
|
152
|
+
| `title` | `string \| null` | `null` |
|
|
153
|
+
| `description` | `string \| null` | `null` |
|
|
154
|
+
| `closeOnBackdrop` | `boolean` | `true` |
|
|
155
|
+
| `closeOnEscape` | `boolean` | `true` |
|
|
156
|
+
| `showClose` | `boolean` | `true` |
|
|
157
|
+
| `iconVariant` | `'info' \| 'success' \| 'warning' \| 'error' \| null` | `null` |
|
|
158
|
+
| `headerIcon` | `string \| null` | `null` |
|
|
159
|
+
| `statusAlignment` | `'horizontal' \| 'vertical' \| null` | `null` |
|
|
160
|
+
| `backdropBlur` | `boolean` | `true` |
|
|
161
|
+
| `fullscreen` | `boolean \| 'auto'` | `'auto'` |
|
|
162
|
+
| `fullBody` | `boolean` | `false` |
|
|
163
|
+
|
|
164
|
+
| output | payload |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `closed` | `void` |
|
|
167
|
+
<!-- api:fin -->
|
|
168
|
+
|
|
169
|
+
Slots: el contenido sin atributo va al **body**; `slot="footer"` va al pie. El pie canon es
|
|
170
|
+
`<sui-modal-footer slot="footer">` (trae su propio padding y borde); un `<div slot="footer">`
|
|
171
|
+
también funciona y recibe padding 16 × 20, gap 12 y alineación a la derecha.
|
|
172
|
+
|
|
173
|
+
Método público: `close()`. Signal pública: `scrollActive` (el body tiene más contenido abajo;
|
|
174
|
+
pinta una sombra sobre el footer).
|
|
175
|
+
|
|
176
|
+
## Trampas
|
|
177
|
+
|
|
178
|
+
- **`slot="footer"` no es opcional.** Sin él, el `sui-modal-footer` se proyecta **dentro del
|
|
179
|
+
body**: queda con doble padding y scrollea con el contenido.
|
|
180
|
+
- **El padding del body es fuente única** (canon, CLAUDE.md 2026-07-21): 20 px. No lo pises con
|
|
181
|
+
`::ng-deep .sui-modal__body` ni le pongas padding lateral o inferior a tus secciones: se
|
|
182
|
+
suman y queda margen doble.
|
|
183
|
+
- **En la web móvil el modal mide como mucho media pantalla.** Si el contenido es largo, el
|
|
184
|
+
body scrollea dentro de ese 50 %. Si eso estorba, `[fullscreen]="true"`.
|
|
185
|
+
- **El `'auto'` se decide al nacer el modal**, leyendo la clase de plataforma de `<html>`. Si
|
|
186
|
+
la app pone `platform-ios` / `platform-android` tarde, los modales creados antes quedan
|
|
187
|
+
centrados.
|
|
188
|
+
- **La app esconde la barra inferior mirando la clase `.sui-modal__container--fullscreen`**
|
|
189
|
+
(`UiOverlayService.SELECTOR_FULLSCREEN` en master-pedidos). Si se renombra aquí, los botones
|
|
190
|
+
del footer vuelven a quedar debajo del tab bar nativo de iOS y dejan de responder.
|
|
191
|
+
- El modal también pide ocultar la barra por el token `SUI_NAVBAR_VISIBILITY` (si la app lo
|
|
192
|
+
provee), con un contador por instancia: dos modales abiertos no se pisan.
|
|
193
|
+
- **Teclado**: en iOS el modal fullscreen resta `--sui-keyboard-h` de su alto; en Android no,
|
|
194
|
+
porque el `dvh` ya se achica solo. Restarlo también en Android deja una franja gris (canon,
|
|
195
|
+
CLAUDE.md). El input enfocado se centra con `scrollIntoView` en las dos.
|
|
196
|
+
- **`headerIcon` gana sobre `iconVariant`** y pinta el círculo en gris. Para un aviso de color,
|
|
197
|
+
solo `iconVariant`.
|
|
198
|
+
- `statusAlignment` sin `iconVariant` ni `headerIcon` no hace nada: vuelve el header normal.
|
|
199
|
+
- Sin `title` y con `[showClose]="false"` no hay header: el body arranca arriba del todo. Así lo usan los modales de permisos y ubicación (`components/location-flow-outlet/location-flow-outlet.html:4`).
|
|
200
|
+
- El host borra el atributo `title` nativo (sin tooltip gris del navegador).
|
|
201
|
+
- `(closed)` se emite **después** de cerrar, por cualquier vía (X, backdrop, Escape o
|
|
202
|
+
`open = false`). Para limpiar estado, escúchalo ahí y no en el botón.
|
|
203
|
+
|
|
204
|
+
## Ejemplo
|
|
205
|
+
|
|
206
|
+
```html
|
|
207
|
+
<!-- Formulario: fullscreen automático en la app, medium en desktop -->
|
|
208
|
+
<sui-modal
|
|
209
|
+
[(open)]="creando"
|
|
210
|
+
title="Crear descuento"
|
|
211
|
+
headerIcon="add-circle-line"
|
|
212
|
+
[closeOnBackdrop]="!guardando()">
|
|
213
|
+
<sui-input label="Nombre" [(value)]="nombre" />
|
|
214
|
+
<sui-modal-footer
|
|
215
|
+
slot="footer"
|
|
216
|
+
[stretch]="true"
|
|
217
|
+
secondaryButton="Cancelar"
|
|
218
|
+
[primaryButton]="{ label: 'Crear descuento', disabled: !valido() }"
|
|
219
|
+
(secondaryClick)="creando.set(false)"
|
|
220
|
+
(primaryClick)="guardar()" />
|
|
221
|
+
</sui-modal>
|
|
222
|
+
|
|
223
|
+
<!-- Confirmación de una línea: siempre centrada -->
|
|
224
|
+
<sui-modal
|
|
225
|
+
[(open)]="confirmando"
|
|
226
|
+
size="small"
|
|
227
|
+
[fullscreen]="false"
|
|
228
|
+
iconVariant="error"
|
|
229
|
+
title="¿Eliminar este gasto?"
|
|
230
|
+
description="Se borra del reporte del mes.">
|
|
231
|
+
<sui-modal-footer
|
|
232
|
+
slot="footer"
|
|
233
|
+
secondaryButton="Cancelar"
|
|
234
|
+
[primaryButton]="{ label: 'Eliminar', color: 'error' }"
|
|
235
|
+
(secondaryClick)="confirmando.set(false)"
|
|
236
|
+
(primaryClick)="eliminar()" />
|
|
237
|
+
</sui-modal>
|
|
238
|
+
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-modal-footer
|
|
3
|
+
selector: sui-modal-footer
|
|
4
|
+
resumen: Pie de modal con la acción principal a la derecha, la secundaria a su izquierda y un slot izquierdo para un checkbox, un link o un texto
|
|
5
|
+
alternativas: [sui-button (botones sueltos fuera de un modal o una hoja), sui-drawer-footer (pie de un panel lateral), sui-notification-footer (acciones de una notificación)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-modal-footer
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Modal/Modal Footer](https://sui-design-system.web.app/?path=/docs/organisms-modal-modal-footer--docs)
|
|
11
|
+
|
|
12
|
+
## Cuándo usarlo / cuándo no
|
|
13
|
+
|
|
14
|
+
**Úsalo** en todo `sui-modal` que tenga botones, siempre con `slot="footer"`, y al pie de una
|
|
15
|
+
`sui-modal-sheet` cuando la hoja termina en una acción. Es la pieza que fija el **orden y el
|
|
16
|
+
peso de los botones** de toda la app: no solo de los modales.
|
|
17
|
+
|
|
18
|
+
**No lo uses** para:
|
|
19
|
+
|
|
20
|
+
- Una fila de botones en medio de una pantalla: ahí van `sui-button` sueltos, pero con **el
|
|
21
|
+
mismo orden** (secundario a la izquierda, primario a la derecha, gap 12).
|
|
22
|
+
- Más de dos botones: no existe un tercero (ver abajo).
|
|
23
|
+
- Proyectar tus propios `sui-button` adentro: el contenido proyectado cae en el slot
|
|
24
|
+
**izquierdo**, no reemplaza a los botones.
|
|
25
|
+
|
|
26
|
+
## Qué variante usar
|
|
27
|
+
|
|
28
|
+
> **La guía del DS dibuja los tipos pero no dice cuándo usar cada uno.** El Figma del DS
|
|
29
|
+
> (nodo 472:566) trae siete tipos: Basic, Checkbox, Information, Toggle, Stepper, Link Button y
|
|
30
|
+
> Stretch. SUI no los tipa: los botones van por `primaryButton` / `secondaryButton` y el resto
|
|
31
|
+
> se proyecta en el slot izquierdo. Las reglas de orden y peso son **canon** (CLAUDE.md
|
|
32
|
+
> «Botones — composición y orden», 2026-07-27); lo demás es **propuesta**.
|
|
33
|
+
>
|
|
34
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app`: 121 footers, sin contar prototipos ni
|
|
35
|
+
> vitrinas (`master-preview`, `master-prototipos`, `mockup-sheets-*`, `dx/*`, `master-rutina`,
|
|
36
|
+
> `master-medico`, `dev-index`), las copias de `components/sui/` ni los `.spec.ts`.
|
|
37
|
+
|
|
38
|
+
### Lo que sí dice la guía del DS
|
|
39
|
+
|
|
40
|
+
- Pie con fondo blanco, **borde arriba** y padding 16 × 20.
|
|
41
|
+
- Acciones a la **derecha**, separadas por 12 px.
|
|
42
|
+
- El tipo **Stretch** reparte los botones a lo ancho.
|
|
43
|
+
- El lado izquierdo lleva, según el tipo, un checkbox con label, un switch con label, un texto
|
|
44
|
+
informativo, un stepper o un link.
|
|
45
|
+
- Dibuja los botones en `small` (36). **SUI los sube a `large` (48)**: ver tamaño.
|
|
46
|
+
|
|
47
|
+
### Cuántas acciones
|
|
48
|
+
|
|
49
|
+
| acciones | cómo | ejemplo real de la app | fuente |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| **1** | solo `primaryButton`. Queda a la derecha, no a todo el ancho (para eso, `[stretch]="true"`) | `pages/super-admin/master-sonidos-push/master-sonidos-push.html:146` («Cerrar») | canon (CLAUDE.md) |
|
|
52
|
+
| **2** | `secondaryButton` + `primaryButton`. El componente los pone en orden: secundario a la izquierda | `pages/driver/driver-dashboard/driver-dashboard.html:1574` («Cancelar» / «Enviar») | canon (CLAUDE.md) |
|
|
53
|
+
| **3+** | **no hay tercer botón.** La tercera acción va como `sui-link-button` en el slot izquierdo | `components/add-product-modal/add-product-modal.html:332` («Vista previa») | canon (CLAUDE.md) |
|
|
54
|
+
|
|
55
|
+
### El slot izquierdo
|
|
56
|
+
|
|
57
|
+
| qué va | cuándo | ejemplo real de la app | fuente |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `sui-link-button` | la tercera acción, de menos peso | `pages/central/central-gastos/central-gastos.html:97` | canon (CLAUDE.md) |
|
|
60
|
+
| `sui-checkbox-label` / `sui-switch-label` | una opción que acompaña la confirmación («No volver a preguntar») | (sin uso hoy) | según el Figma del DS |
|
|
61
|
+
| texto | un dato que se lee antes de confirmar («Llega a las 8:30») | `components/add-product-modal/add-product-modal.html:359` | según el Figma del DS |
|
|
62
|
+
| `sui-counter-input` (stepper) | elegir cantidad junto al botón de agregar | `pages/customer/restaurant-menu/restaurant-menu.html:461` | según el Figma del DS |
|
|
63
|
+
|
|
64
|
+
Con `stretch` y algo en el slot izquierdo, se reparte 50 % slot / 50 % botones.
|
|
65
|
+
|
|
66
|
+
### El color de los botones
|
|
67
|
+
|
|
68
|
+
| botón | variante | regla | fuente |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| secundario | `stroke` `neutral`, **fijo** | no se configura: `SuiFooterButtonConfig.color` solo lo lee el primario | canon (CLAUDE.md) |
|
|
71
|
+
| primario | `filled` `primary` | la acción que termina el flujo | canon (CLAUDE.md) |
|
|
72
|
+
| primario destructivo | `filled` con `color: 'error'` | eliminar, cancelar un pedido, rechazar. **El destructivo va siempre en el primario**, a la derecha. Hoy 28 | canon (CLAUDE.md) |
|
|
73
|
+
|
|
74
|
+
### `stretch`
|
|
75
|
+
|
|
76
|
+
| valor | cuándo | fuente |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `true` | en el modal a pantalla completa de mobile: botones a lo ancho, 50/50 | canon (CANON-SUI §6.3) |
|
|
79
|
+
| `false` (default) | modal centrado en desktop: botones a la derecha, del ancho de su texto | según el Figma del DS (tipo Basic) |
|
|
80
|
+
|
|
81
|
+
El componente **no tiene `@media`**: `stretch` se ve igual en las dos plataformas. Hoy 95 de 121
|
|
82
|
+
lo pasan fijo en `true`, así que también en desktop los botones van 50/50. Propuesta:
|
|
83
|
+
`[stretch]="bp.isMobile()"` para que desktop vuelva al tipo Basic.
|
|
84
|
+
|
|
85
|
+
**Usos de hoy que rompen el canon:**
|
|
86
|
+
|
|
87
|
+
- **3 footers con un solo botón en `secondaryButton`** → pasarlo a `primaryButton`:
|
|
88
|
+
`pages/driver/driver-dashboard/driver-dashboard.html:1272`, `:1419` y `:1555` (los tres
|
|
89
|
+
«Cerrar»). Contraargumento a decidir: en un modal que solo informa, un «Cerrar» relleno de
|
|
90
|
+
naranja grita más que el contenido; si se prefiere así, hay que escribirlo como excepción.
|
|
91
|
+
- **1 pie hecho a mano** con dos `sui-button` en un `<div slot="footer">` →
|
|
92
|
+
`sui-modal-footer`: `components/copy-text-dialog/copy-text-dialog.html:15`.
|
|
93
|
+
- Botones `sui-button` proyectados dentro del footer: 0.
|
|
94
|
+
|
|
95
|
+
## Tamaño en mobile y en web
|
|
96
|
+
|
|
97
|
+
| uso | mobile | web | fuente |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| botones | `large` (48) | `large` (48) | canon (CLAUDE.md, decisión de marca 2026-07-23; el Figma dibuja `small`) |
|
|
100
|
+
| reparto | `[stretch]="true"`, 50/50 | botones a la derecha | canon en mobile (CANON-SUI §6.3), propuesta en web |
|
|
101
|
+
| borde de arriba | sin borde en el modal a pantalla completa | con borde | componente (`sui-modal.scss`) |
|
|
102
|
+
|
|
103
|
+
`buttonSize` existe para un caso puntual, no para «achicar porque se ve grande». Hoy 0 usos.
|
|
104
|
+
|
|
105
|
+
`CANON-SUI.md` §6.3 todavía dice «botones `small`»: quedó viejo frente a la decisión del
|
|
106
|
+
2026-07-23 y al default del componente.
|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
<!-- api:inicio -->
|
|
111
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
112
|
+
|
|
113
|
+
| input | tipo | default |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `primaryButton` | `SuiFooterButtonConfig \| string \| null` | `null` |
|
|
116
|
+
| `secondaryButton` | `SuiFooterButtonConfig \| string \| null` | `null` |
|
|
117
|
+
| `stretch` | `boolean` | `false` |
|
|
118
|
+
| `buttonSize` | `'2xsmall' \| 'xsmall' \| 'small' \| 'medium' \| 'large' \| 'xlarge' \| null` | `null` |
|
|
119
|
+
|
|
120
|
+
| output | payload |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `primaryClick` | `void` |
|
|
123
|
+
| `secondaryClick` | `void` |
|
|
124
|
+
<!-- api:fin -->
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// Exportada desde sui-button
|
|
128
|
+
interface SuiFooterButtonConfig {
|
|
129
|
+
label: string;
|
|
130
|
+
disabled?: boolean;
|
|
131
|
+
color?: 'primary' | 'error'; // solo lo lee el primario
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Un `string` es atajo de `{ label }`. `null`, `''` o un config con `label` vacío no dibuja el
|
|
136
|
+
botón. `buttonSize` en `null` resuelve a `large`.
|
|
137
|
+
|
|
138
|
+
Slot: `<ng-content>` sin selector → el bloque **izquierdo**.
|
|
139
|
+
|
|
140
|
+
## Trampas
|
|
141
|
+
|
|
142
|
+
- **Dentro de `sui-modal` lleva `slot="footer"`.** Sin él se proyecta dentro del body: doble
|
|
143
|
+
padding y scrollea con el contenido. En `sui-modal-sheet` no hay slot de footer: va al final
|
|
144
|
+
del contenido, sin `slot` (`pages/customer/checkout/checkout.html:490`).
|
|
145
|
+
- **Lo que proyectas va a la izquierda.** Dos `sui-button` adentro quedan a la izquierda de los
|
|
146
|
+
botones del componente, con su propio tamaño y sin el orden canon.
|
|
147
|
+
- **No hay `loading`.** Para «guardando», cambia el label y deshabilita:
|
|
148
|
+
`{ label: enviando() ? 'Enviando…' : 'Enviar', disabled: enviando() }`.
|
|
149
|
+
- **`color` en el secundario no hace nada**: siempre sale `stroke neutral`. Un «Eliminar»
|
|
150
|
+
rojo a la izquierda no se puede armar, y es a propósito.
|
|
151
|
+
- **`stretch` no cambia el orden**: el primario sigue a la derecha, nunca arriba.
|
|
152
|
+
- Los botones son `type="button"`: el footer **no envía un `<form>`**. Llama al guardado en
|
|
153
|
+
`(primaryClick)`.
|
|
154
|
+
- Dentro de `sui-modal` el footer recibe una sombra arriba mientras el body tiene más contenido
|
|
155
|
+
para scrollear (`scrollActive`).
|
|
156
|
+
|
|
157
|
+
## Ejemplo
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<!-- 2 acciones, destructiva -->
|
|
161
|
+
<sui-modal-footer
|
|
162
|
+
slot="footer"
|
|
163
|
+
secondaryButton="Cancelar"
|
|
164
|
+
[primaryButton]="{ label: 'Eliminar', color: 'error' }"
|
|
165
|
+
(secondaryClick)="cerrar()"
|
|
166
|
+
(primaryClick)="borrar()" />
|
|
167
|
+
|
|
168
|
+
<!-- 3 acciones: la tercera es un link en el slot izquierdo -->
|
|
169
|
+
<sui-modal-footer
|
|
170
|
+
slot="footer"
|
|
171
|
+
[stretch]="true"
|
|
172
|
+
secondaryButton="Cancelar"
|
|
173
|
+
[primaryButton]="{ label: guardando() ? 'Guardando…' : 'Guardar', disabled: guardando() }"
|
|
174
|
+
(secondaryClick)="cerrar()"
|
|
175
|
+
(primaryClick)="guardar()">
|
|
176
|
+
<sui-link-button leftIcon="eye-line" (clicked)="verPrevia()">Vista previa</sui-link-button>
|
|
177
|
+
</sui-modal-footer>
|
|
178
|
+
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-modal-header
|
|
3
|
+
selector: sui-modal-header
|
|
4
|
+
resumen: Cabecera de modal con título, descripción, ícono opcional y la X de cerrar; sui-modal ya la dibuja sola
|
|
5
|
+
alternativas: [sui-modal (ya trae este header por title/description/iconVariant), sui-drawer-header (cabecera de un panel lateral), sui-page-header (título de una pantalla)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-modal-header
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Modal/Modal Header](https://sui-design-system.web.app/?path=/docs/organisms-modal-modal-header--docs)
|
|
11
|
+
|
|
12
|
+
## Cuándo usarlo / cuándo no
|
|
13
|
+
|
|
14
|
+
**No lo uses dentro de `sui-modal`.** El modal ya lo dibuja con sus inputs `title`,
|
|
15
|
+
`description`, `iconVariant`, `headerIcon` y `showClose`, y elige el tamaño solo. Ponerlo a mano
|
|
16
|
+
en el body da **dos cabeceras**.
|
|
17
|
+
|
|
18
|
+
**Úsalo** solo para armar un panel o una superficie propia que tiene que verse como un modal y
|
|
19
|
+
no puede ser `sui-modal`: un panel fijo en desktop, un overlay propio. Es el par de
|
|
20
|
+
`sui-modal-footer` para esos casos.
|
|
21
|
+
|
|
22
|
+
Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos directos**. Los 128 modales de
|
|
23
|
+
la app lo usan a través de `sui-modal`.
|
|
24
|
+
|
|
25
|
+
## Qué variante usar
|
|
26
|
+
|
|
27
|
+
> **La guía del DS dibuja los tipos pero no dice cuándo usar cada uno.** El Figma del DS
|
|
28
|
+
> (nodo 466:4778) trae seis tipos —Basic, Left Icon, Error, Warning, Success, Information— en dos
|
|
29
|
+
> tamaños, Small (56) y Medium (80). Lo marcado **propuesta** falta aprobar.
|
|
30
|
+
|
|
31
|
+
### Lo que sí dice la guía del DS
|
|
32
|
+
|
|
33
|
+
- **Medium (80 px de alto mínimo)**: título + descripción. El ícono va en un círculo de 20 px.
|
|
34
|
+
- **Small (56)**: solo título. El ícono va **plano, de 24 px, sin círculo**.
|
|
35
|
+
- Título en Label/Small (14/500), descripción en Paragraph/X-Small (12/400) gris.
|
|
36
|
+
- Borde abajo, padding 16 / 20 y la X de cerrar arriba a la derecha.
|
|
37
|
+
|
|
38
|
+
### `size`
|
|
39
|
+
|
|
40
|
+
| size | cuándo | fuente |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `medium` (default) | hay `description` | según el Figma del DS |
|
|
43
|
+
| `small` | solo título | según el Figma del DS |
|
|
44
|
+
|
|
45
|
+
Es la misma regla que aplica `sui-modal`: `[size]="description ? 'medium' : 'small'"`. El
|
|
46
|
+
componente **no la aplica solo**: si lo usas suelto, elige tú.
|
|
47
|
+
|
|
48
|
+
### Ícono
|
|
49
|
+
|
|
50
|
+
| input | qué dibuja | cuándo | fuente |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| ninguno | Basic: solo texto | el caso normal | según el Figma del DS |
|
|
53
|
+
| `iconVariant` | ícono del estado en un círculo de su color | el panel **es** un aviso de error, advertencia, éxito o información | propuesta |
|
|
54
|
+
| `icon` | ícono propio (Remix) en un círculo gris | identifica qué se edita (`edit-line`, `user-add-line`). Gana sobre `iconVariant` | propuesta |
|
|
55
|
+
|
|
56
|
+
## Tamaño en mobile y en web
|
|
57
|
+
|
|
58
|
+
El `size` de este componente **no es de plataforma**: lo decide si hay descripción. Es igual en
|
|
59
|
+
mobile y en web. La X mide 24 px en las dos (`sui-compact-button` `large`).
|
|
60
|
+
|
|
61
|
+
| uso | mobile | web | fuente |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| con descripción | `medium` | `medium` | según el Figma del DS |
|
|
64
|
+
| solo título | `small` | `small` | según el Figma del DS |
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
<!-- api:inicio -->
|
|
69
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
70
|
+
|
|
71
|
+
| input | tipo | default |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `title` | `string` | `''` |
|
|
74
|
+
| `description` | `string \| null` | `null` |
|
|
75
|
+
| `size` | `'medium' \| 'small'` | `'medium'` |
|
|
76
|
+
| `iconVariant` | `'info' \| 'success' \| 'warning' \| 'error' \| null` | `null` |
|
|
77
|
+
| `icon` | `string \| null` | `null` |
|
|
78
|
+
| `showClose` | `boolean` | `true` |
|
|
79
|
+
| `titleId` | `string \| null` | `null` |
|
|
80
|
+
| `descriptionId` | `string \| null` | `null` |
|
|
81
|
+
|
|
82
|
+
| output | payload |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `closed` | `void` |
|
|
85
|
+
<!-- api:fin -->
|
|
86
|
+
|
|
87
|
+
`titleId` y `descriptionId` ponen el `id` al `<h2>` y al `<p>`: sirven para el
|
|
88
|
+
`aria-labelledby` / `aria-describedby` del contenedor que hace de diálogo.
|
|
89
|
+
|
|
90
|
+
## Trampas
|
|
91
|
+
|
|
92
|
+
- **Los nombres no coinciden con `sui-modal`**: aquí el ícono propio es `icon`; en el modal es
|
|
93
|
+
`headerIcon`. `iconVariant` sí se llama igual.
|
|
94
|
+
- **`size` no se ajusta solo** a la descripción (en `sui-modal` sí). Con `description` y
|
|
95
|
+
`size="small"` la descripción se ve igual, pero el ícono pasa a plano de 24.
|
|
96
|
+
- **`closed` no cierra nada**: solo avisa que se tocó la X. Cerrar el panel es tuyo.
|
|
97
|
+
- Sin `titleId`, el contenedor no tiene nombre accesible: si haces un diálogo, pásalo y úsalo
|
|
98
|
+
en `aria-labelledby`.
|
|
99
|
+
- El host borra el atributo `title` nativo (sin tooltip gris del navegador).
|
|
100
|
+
|
|
101
|
+
## Ejemplo
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<!-- Panel propio con la cabecera de modal -->
|
|
105
|
+
<section class="panel" role="dialog" aria-labelledby="filtros-titulo">
|
|
106
|
+
<sui-modal-header
|
|
107
|
+
size="small"
|
|
108
|
+
title="Filtros"
|
|
109
|
+
titleId="filtros-titulo"
|
|
110
|
+
(closed)="cerrarPanel()" />
|
|
111
|
+
…
|
|
112
|
+
<sui-modal-footer secondaryButton="Limpiar" primaryButton="Aplicar"
|
|
113
|
+
(secondaryClick)="limpiar()" (primaryClick)="aplicar()" />
|
|
114
|
+
</section>
|
|
115
|
+
```
|