@ajuarezso/sui-design-system 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/docs/INDEX.md +51 -51
  2. package/docs/sui-alert/README.md +13 -0
  3. package/docs/sui-badge/README.md +17 -0
  4. package/docs/sui-banner/README.md +190 -0
  5. package/docs/sui-breadcrumb-item/README.md +96 -0
  6. package/docs/sui-breadcrumbs/README.md +122 -0
  7. package/docs/sui-button/README.md +17 -0
  8. package/docs/sui-card/README.md +168 -0
  9. package/docs/sui-chart-legend/README.md +140 -0
  10. package/docs/sui-chart-legend-dot/README.md +118 -0
  11. package/docs/sui-checkbox-card/README.md +150 -0
  12. package/docs/sui-compact-select/README.md +127 -0
  13. package/docs/sui-compact-select-for-input/README.md +117 -0
  14. package/docs/sui-content-card/README.md +147 -0
  15. package/docs/sui-content-label/README.md +148 -0
  16. package/docs/sui-counter-input/README.md +145 -0
  17. package/docs/sui-date-selector/README.md +98 -0
  18. package/docs/sui-datepicker/README.md +193 -0
  19. package/docs/sui-day-cell/README.md +118 -0
  20. package/docs/sui-day-label/README.md +81 -0
  21. package/docs/sui-drawer-footer/README.md +127 -0
  22. package/docs/sui-drawer-header/README.md +140 -0
  23. package/docs/sui-empty-state/README.md +145 -0
  24. package/docs/sui-file-upload/README.md +154 -0
  25. package/docs/sui-file-upload-card/README.md +129 -0
  26. package/docs/sui-image-upload/README.md +200 -0
  27. package/docs/sui-image-upload-profile/README.md +145 -0
  28. package/docs/sui-inline-input/README.md +104 -0
  29. package/docs/sui-inline-select/README.md +101 -0
  30. package/docs/sui-input/README.md +244 -0
  31. package/docs/sui-integration-switch/README.md +142 -0
  32. package/docs/sui-notification/README.md +179 -0
  33. package/docs/sui-notification-footer/README.md +98 -0
  34. package/docs/sui-notification-tab-menu/README.md +112 -0
  35. package/docs/sui-otp-input/README.md +116 -0
  36. package/docs/sui-pagination/README.md +146 -0
  37. package/docs/sui-password-strength/README.md +99 -0
  38. package/docs/sui-period-range/README.md +93 -0
  39. package/docs/sui-popover/README.md +178 -0
  40. package/docs/sui-product-card/README.md +173 -0
  41. package/docs/sui-radio-card/README.md +160 -0
  42. package/docs/sui-rating/README.md +7 -6
  43. package/docs/sui-rich-editor/README.md +114 -0
  44. package/docs/sui-select/README.md +180 -0
  45. package/docs/sui-stepper/README.md +166 -0
  46. package/docs/sui-store-card/README.md +147 -0
  47. package/docs/sui-switch-card/README.md +149 -0
  48. package/docs/sui-tab-chips/README.md +185 -0
  49. package/docs/sui-tab-menu-horizontal/README.md +142 -0
  50. package/docs/sui-tab-menu-vertical/README.md +136 -0
  51. package/docs/sui-tag-input/README.md +120 -0
  52. package/docs/sui-textarea/README.md +157 -0
  53. package/docs/sui-time-picker/README.md +237 -0
  54. package/docs/sui-toast/README.md +190 -0
  55. package/docs/sui-tooltip/README.md +201 -0
  56. package/fesm2022/ajuarezso-sui-design-system.mjs +273 -231
  57. package/fesm2022/ajuarezso-sui-design-system.mjs.map +1 -1
  58. package/package.json +1 -1
  59. package/types/ajuarezso-sui-design-system.d.ts +208 -52
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: sui-notification
3
+ selector: sui-notification
4
+ resumen: Fila de la bandeja de notificaciones, con quién, qué pasó, hace cuánto y si ya se leyó
5
+ alternativas: [sui-toast (aviso efímero, incluido el «notification» de esquina de la guía), sui-alert (aviso que se queda en su contexto), sui-banner (toda la app)]
6
+ ---
7
+
8
+ # sui-notification
9
+
10
+ Story: [Molecules/Feedback/Notification](https://sui-design-system.web.app/?path=/docs/molecules-feedback-notification--docs)
11
+
12
+ ## Primero: dos cosas distintas con el mismo nombre
13
+
14
+ - **En la guía del DS**, una «notification» es un aviso **temporal en una esquina** con título,
15
+ descripción y una acción (un pago, una actualización). En SUI ese caso **es un `sui-toast`** con
16
+ `description` y `action`.
17
+ - **En SUI**, `sui-notification` es la **fila de la bandeja**: el panel de la campana del Figma del
18
+ DS (foto, título, descripción, hora, punto de no leída). Este README es sobre eso.
19
+
20
+ El componente además arrastra un tercer uso: un aviso con tipo y relleno (`variant`, `variantStyle`)
21
+ igual al de `sui-alert`. Ese uso no va: para eso está el alert.
22
+
23
+ ## Cuándo usarlo / cuándo no
24
+
25
+ **Úsalo** para cada aviso de una **bandeja** que la persona revisa cuando quiere: «Tu pedido llegó»,
26
+ «Nuevo pedido de Pollería El Dorado», «Recarga aprobada». Lleva **la hora**, se marca como leído y
27
+ se puede volver a abrir.
28
+
29
+ **No lo uses** para:
30
+
31
+ - Avisar algo en el momento: `sui-toast` (aunque tenga título, descripción y acción).
32
+ - Un aviso dentro de una pantalla: `sui-alert`.
33
+ - Una lista de actividad sin leído/no leído (el historial de un pedido): `sui-activity-feed`.
34
+
35
+ Regla corta (canon, la misma del alert): **contexto → alert · toda la app → banner · efímero →
36
+ toast · bandeja → notification.**
37
+
38
+ ## Qué variante usar
39
+
40
+ > La guía del DS no tiene esta fila (su «notification» es otra cosa, ver arriba). Lo de abajo sale
41
+ > del Figma del DS y del componente («según el componente»), de lo ya aprobado («canon») o es
42
+ > **propuesta**.
43
+ >
44
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**. La bandeja de la app
45
+ > (`pages/notifications/notifications.html:98`, la misma pantalla en las rutas de los 4 roles) dibuja
46
+ > sus filas a mano.
47
+
48
+ ### Qué lleva cada fila
49
+
50
+ | prop | valor | cuándo | fuente |
51
+ |---|---|---|---|
52
+ | `avatar` | `src` con la foto | **Siempre**: la cara o el logo de quien generó el aviso (el local del pedido, el repartidor que lo lleva). Una foto se reconoce antes que un texto. | propuesta |
53
+ | `avatar` | `{ initials }` | Solo si no hay foto. Ojo: sale en un círculo negro, **no** con el color por rol (ver Trampas). | según el componente |
54
+ | `avatar` | el isotipo de la app | Avisos **del sistema** sin una persona detrás («Tu plan vence en 3 días»): así toda la bandeja queda en filas iguales. | propuesta |
55
+ | `title` | frase corta | Qué pasó: «Tu pedido llegó». Sentence case, sin punto final. | canon (copy de la app) |
56
+ | `description` | una o dos frases | El detalle que decide si abrirla: «Pollería El Dorado · S/ 42.50». | propuesta |
57
+ | `timestamp` | texto ya formateado | **Siempre**: la hora es lo que hace bandeja a la bandeja. «Hace 5 min», «Ayer 18:40». | canon (bandeja con hora) · formato: propuesta |
58
+ | `unread` | `true` | Hasta que la persona la abre. El punto es naranja (el primario de la app). | según el componente |
59
+ | `showMore` | `true` | No usar hoy: el botón ⋮ solo aparece con el mouse encima. **Cuándo sí:** en web, acciones por fila (marcar como leída, archivar). | propuesta |
60
+ | `active` | `true` | No usar hoy: pinta la fila como si tuviera el mouse encima. **Cuándo sí:** navegar la bandeja con las flechas del teclado (ver `sui-notification-footer`). | propuesta |
61
+ | `dismissible` | `true` | No usar hoy: los avisos de la bandeja se leen, no se cierran. **Cuándo sí:** una bandeja donde se borran avisos uno por uno. | propuesta |
62
+
63
+ ### `type` — el cuerpo de la fila
64
+
65
+ | type | cuándo | fuente |
66
+ |---|---|---|
67
+ | `basic` (default) | Todas las de hoy: título, descripción y hora. | según el componente |
68
+ | `button` | No usar hoy. **Cuándo sí:** una solicitud que se resuelve desde la misma bandeja, con «Rechazar» / «Aprobar» en `slot="actions"` (por ejemplo, una invitación a un equipo). Secundario a la izquierda, primario a la derecha. | propuesta · orden: canon |
69
+ | `message` | No usar hoy. **Cuándo sí:** el aviso trae lo que escribió otra persona (la nota del cliente, un mensaje del repartidor), en la burbuja. | propuesta |
70
+ | `file` | No usar hoy: la app no manda adjuntos. **Cuándo sí:** un comprobante o una boleta adjunta. | propuesta |
71
+
72
+ ### `variant` y `variantStyle` — el modelo de alert
73
+
74
+ | prop | valor | cuándo | fuente |
75
+ |---|---|---|---|
76
+ | `variant` | `default` (default) | **Siempre** en la bandeja: el tipo lo dice la foto y el texto, no un color. | propuesta |
77
+ | `variant` | `info` · `success` · `warning` · `error` · `feature` | No usar: con tipo y sin foto, la fila se vuelve un alert con borde y sombra. **Cuándo sí:** ninguno; ese aviso es un `sui-alert`. | propuesta (sigue la regla canon) |
78
+ | `variantStyle` | `stroke` (default) | Siempre. Con foto, la fila es plana: sin borde ni sombra. | según el componente |
79
+ | `variantStyle` | `filled` · `light` · `lighter` | No usar: pintan la fila de color, que es lo que hace el alert. **Cuándo sí:** ninguno. | propuesta |
80
+ | `showIcon` | `true` (default) | No cambia nada con foto; el ícono de tipo solo sale en el modelo de alert. | según el componente |
81
+
82
+ ### Slots
83
+
84
+ | slot | cuándo | fuente |
85
+ |---|---|---|
86
+ | `slot="company-logo"` | Logo de 16 px antes de la descripción: el local, cuando la foto es la del repartidor. | propuesta |
87
+ | `slot="actions"` | Los dos botones de `type="button"`. | según el componente |
88
+ | `slot="message"` · `slot="file"` | El cuerpo de `type="message"` y `type="file"`. | según el componente |
89
+
90
+ ### Usos de hoy que romperían la regla
91
+
92
+ - **La bandeja dibuja sus filas a mano — 1 pantalla, 4 rutas:** `pages/notifications/notifications.html:98`
93
+ (ícono de color por tipo, fila teñida si no se leyó, punto a la derecha). **A discutir**: no es un
94
+ cambio 1:1. Con el componente, el tipo se ve en la foto y no en un ícono de color, y la no leída
95
+ lleva solo el punto, sin teñir la fila.
96
+
97
+ ## Tamaño en mobile y en web
98
+
99
+ La fila **no tiene `size`**: foto de 40 px, 12 px de relleno, texto de 14.
100
+
101
+ | uso | mobile | web | fuente |
102
+ |---|---|---|---|
103
+ | la fila | toda la fila se toca para abrirla: mide 64 px o más | igual | según el componente |
104
+ | ⋮ (`showMore`) | no aplica: aparece con el mouse | 24 px | según el componente |
105
+ | la X (`dismissible`) | se ve de 20 px; **se toca en 40 × 40** | igual | canon (aprobado por el dueño, 03-10-2026): área táctil invisible, extensión SUI |
106
+ | botones de `type="button"` | los pone quien la usa: `[size]="bp.isMobile() ? 'large' : 'xsmall'"` | `xsmall` | propuesta |
107
+
108
+ ## «No usar»: por qué no hoy y cuándo sí
109
+
110
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
111
+ |---|---|---|
112
+ | el modelo de alert (`variant` ≠ `default`, rellenos de color) | duplica a `sui-alert` | ninguno: es un alert |
113
+ | `type="button"` | nada se resuelve desde la bandeja | aceptar o rechazar una solicitud (una invitación a un equipo) desde la bandeja |
114
+ | `type="message"` | los avisos no traen texto de otra persona | la nota del cliente o un mensaje del repartidor |
115
+ | `type="file"` | no hay adjuntos | un comprobante o una boleta |
116
+ | `showMore` | solo se ve con mouse | acciones por fila en web |
117
+ | `active` | no se navega con teclado | bandeja con flechas del teclado |
118
+ | `dismissible` | se leen, no se cierran | una bandeja donde se borra aviso por aviso |
119
+
120
+ ## API
121
+
122
+ <!-- api:inicio -->
123
+ <!-- generada: no editar a mano — `npm run docs` -->
124
+
125
+ | input | tipo | default |
126
+ |---|---|---|
127
+ | `variant` | `'info' \| 'success' \| 'warning' \| 'error' \| 'feature' \| 'default'` | `'default'` |
128
+ | `variantStyle` | `'filled' \| 'light' \| 'lighter' \| 'stroke'` | `'stroke'` |
129
+ | `title` | `string \| null` | `null` |
130
+ | `description` | `string \| null` | `null` |
131
+ | `timestamp` | `string \| null` | `null` |
132
+ | `avatar` | `SuiAvatarConfig \| string \| null` | `null` |
133
+ | `showIcon` | `boolean` | `true` |
134
+ | `dismissible` | `boolean` | `false` |
135
+ | `unread` | `boolean` | `false` |
136
+ | `type` | `'basic' \| 'file' \| 'button' \| 'message'` | `'basic'` |
137
+ | `showMore` | `boolean` | `false` |
138
+ | `active` | `boolean` | `false` |
139
+
140
+ | output | payload |
141
+ |---|---|
142
+ | `dismissed` | `void` |
143
+ | `moreClicked` | `MouseEvent` |
144
+ <!-- api:fin -->
145
+
146
+ Slots: `[slot=company-logo]`, `[slot=file]`, `[slot=message]`, el contenido libre, `[slot=actions]`.
147
+
148
+ ## Trampas
149
+
150
+ - **La X se toca en 40 × 40** aunque se vea de 20 (un `::before` invisible sobre su `<button>`,
151
+ desde 03-10-2026): en una fila que se toca entera, el toque a 10 px de la X la cierra en vez de
152
+ abrirla.
153
+ - **Es el avatar el que decide el modelo**: con `avatar` es una fila plana (`role="article"`); sin
154
+ `avatar` es un alert con borde y sombra (`role="status"`, que el lector de pantalla anuncia).
155
+ - **El avatar no es `sui-avatar`**: lo dibuja la fila, en un círculo **negro** con iniciales.
156
+ Ignora `color`, así que no respeta el color por rol de la app (local naranja, repartidor verde,
157
+ cliente azul). Con foto no se nota; sin foto, sí.
158
+ - **La hora cambia de lugar**: sin avatar va a la derecha del título; con avatar, delante de la
159
+ descripción («Hace 5 min ∙ …»).
160
+ - `timestamp` es texto: el formato («Hace 5 min») lo arma quien la usa.
161
+ - `feature` usa `sparkling-fill` aquí y en el alert, y `magic-fill` en el banner.
162
+ - `dismissible` solo emite `(dismissed)`: la fila **no se esconde sola** (a diferencia de alert y
163
+ banner). La saca quien la usa.
164
+ - El ⋮ de `showMore` se ve solo con el mouse encima o con `active`: en mobile no aparece.
165
+ - **`showMore` y `dismissible` juntos se pisan**: el ⋮ y la X van los dos arriba a la derecha.
166
+ - Con avatar (o con `type` distinto de `basic`) y **sin `description`**, queda un «∙» colgando
167
+ después de la hora («Hace 8 min ∙»).
168
+ - Borra el `title` nativo del host.
169
+
170
+ ## Ejemplo
171
+
172
+ ```html
173
+ <div role="list">
174
+ <sui-notification role="listitem"
175
+ [avatar]="pedido.localLogo" title="Tu pedido llegó"
176
+ description="Pollería El Dorado · S/ 42.50" timestamp="Hace 5 min" [unread]="!n.read"
177
+ (click)="abrir(n)" />
178
+ </div>
179
+ ```
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: sui-notification-footer
3
+ selector: sui-notification-footer
4
+ resumen: Pie del panel desplegable de notificaciones (web): ayuda de teclado a la izquierda y un link a los ajustes a la derecha
5
+ alternativas: [sui-link-button (un link suelto a los ajustes), sui-drawer-footer (pie de un panel lateral), sui-modal-footer (pie con botones)]
6
+ ---
7
+
8
+ # sui-notification-footer
9
+
10
+ Story: [Molecules/Feedback/Notification Footer](https://sui-design-system.web.app/?path=/docs/molecules-feedback-notification-footer--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** solo como **pie del panel de la campana** en web: el desplegable que abre la bandeja desde
15
+ la barra superior, con filas `sui-notification` y la cabecera `sui-notification-tab-menu`. Dice
16
+ «Usa ↑ ↓ para moverte» y lleva el link a los ajustes de notificaciones.
17
+
18
+ **No lo uses** para:
19
+
20
+ - La pantalla de notificaciones (la bandeja a pantalla completa): ahí los ajustes ya están en la
21
+ misma pantalla y nadie navega con flechas.
22
+ - Mobile: la mitad del componente es una ayuda de **teclado**.
23
+ - El pie de cualquier otro panel: `sui-drawer-footer` o `sui-modal-footer`.
24
+
25
+ ## Qué variante usar
26
+
27
+ > La guía del DS no tiene página para este componente: sale del Figma del DS (pie del panel de
28
+ > notificaciones). Todo lo de abajo es **propuesta**, salvo lo marcado «según el componente».
29
+ >
30
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**. La app no tiene panel
31
+ > desplegable de campana: su bandeja es una pantalla (`pages/notifications/`).
32
+
33
+ | prop | valor | cuándo | fuente |
34
+ |---|---|---|---|
35
+ | `usePrefix` · `useSuffix` | `'Usa'` · `'para moverte'` | Ya son los defaults (03-10-2026); no hace falta pasarlos. | canon (copy de la app: tuteo, español) |
36
+ | `manage` | `'Ajustes de notificaciones'` | El link a los ajustes. Emite `(manageClick)`. | propuesta |
37
+ | `manage` | `{ label, href }` | Cuando los ajustes son otra ruta: se dibuja un `<a>` de verdad. | según el componente |
38
+ | `manage` | `true` (default) | Dice «Administrar notificaciones» (en español desde 03-10-2026). | propuesta |
39
+ | `manage` | `false` · `null` | Cuando los ajustes ya están a la vista en el mismo panel. | propuesta |
40
+
41
+ ## Tamaño en mobile y en web
42
+
43
+ **No tiene `size`**: texto de 12 y link `small` (16 px de alto).
44
+
45
+ | uso | mobile | web | fuente |
46
+ |---|---|---|---|
47
+ | el pie entero | no aplica: es ayuda de teclado; en mobile no se muestra | unos 48 px de alto | propuesta |
48
+ | el link de ajustes | si hiciera falta, un `sui-link-button` `large` suelto | `small` (16 px) | propuesta |
49
+
50
+ ```html
51
+ @if (!bp.isMobile()) {
52
+ <sui-notification-footer usePrefix="Usa" useSuffix="para moverte"
53
+ manage="Ajustes de notificaciones" (manageClick)="abrirAjustes()" />
54
+ }
55
+ ```
56
+
57
+ ## «No usar»: por qué no hoy y cuándo sí
58
+
59
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
60
+ |---|---|---|
61
+ | el componente | no hay panel de campana | si el panel del local o el super admin suman una campana con la bandeja desplegable en la barra superior (web) |
62
+ | `manage` con el texto por defecto | está en inglés | nunca: siempre un texto propio |
63
+
64
+ ## API
65
+
66
+ <!-- api:inicio -->
67
+ <!-- generada: no editar a mano — `npm run docs` -->
68
+
69
+ | input | tipo | default |
70
+ |---|---|---|
71
+ | `usePrefix` | `string` | `'Usa'` |
72
+ | `useSuffix` | `string` | `'para moverte'` |
73
+ | `manage` | `SuiLinkConfig \| string \| boolean \| null` | `true` |
74
+
75
+ | output | payload |
76
+ |---|---|
77
+ | `manageClick` | `MouseEvent` |
78
+ <!-- api:fin -->
79
+
80
+ ```ts
81
+ interface SuiLinkConfig { label: string; href?: string; target?: string; rel?: string; }
82
+ ```
83
+
84
+ ## Trampas
85
+
86
+ - **Textos por defecto en español** desde 03-10-2026: «Usa ↑ ↓ para moverte» y «Administrar
87
+ notificaciones» (antes «Use», «to navigate» y «Manage Notification»).
88
+ - Las teclas ↑ ↓ están fijas: no se pueden cambiar por otras.
89
+ - Con `href` y `target="_blank"` agrega `rel="noopener noreferrer"` solo.
90
+ - Ocupa el 100 % del ancho de su contenedor (el Figma lo dibuja de 540 px).
91
+ - Borra el `title` nativo del host.
92
+
93
+ ## Ejemplo
94
+
95
+ ```html
96
+ <sui-notification-footer usePrefix="Usa" useSuffix="para moverte"
97
+ [manage]="{ label: 'Ajustes de notificaciones', href: '/empresa/notificaciones' }" />
98
+ ```
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: sui-notification-tab-menu
3
+ selector: sui-notification-tab-menu
4
+ resumen: Cabecera con pestañas del panel desplegable de notificaciones (web), con conteo de no leídas y botón de filtro
5
+ alternativas: [sui-tab-chips (pestañas de la bandeja a pantalla completa, en mobile), sui-tab-menu-horizontal (pestañas de una pantalla), sui-segmented-control (cambiar de vista con 2–4 opciones)]
6
+ ---
7
+
8
+ # sui-notification-tab-menu
9
+
10
+ Story: [Molecules/Feedback/Notification Tab Menu](https://sui-design-system.web.app/?path=/docs/molecules-feedback-notification-tab-menu--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** solo como **cabecera del panel de la campana** en web: «Todas · No leídas (3) · Pedidos |
15
+ Archivadas» con el filtro a la derecha, encima de las filas `sui-notification`.
16
+
17
+ **No lo uses** para:
18
+
19
+ - La bandeja a pantalla completa y en mobile: la app usa `sui-tab-chips` («Todas», «No leídas (3)»)
20
+ en `pages/notifications/notifications.html:47`, que se toca mejor con el dedo. Es lo correcto.
21
+ - Las pestañas de cualquier otra pantalla: `sui-tab-menu-horizontal`.
22
+
23
+ ## Qué variante usar
24
+
25
+ > La guía del DS no tiene página para este componente: sale del Figma del DS (cabecera del panel
26
+ > de notificaciones). Todo lo de abajo es **propuesta**, salvo lo marcado «según el componente».
27
+ >
28
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos** (no hay panel de campana).
29
+
30
+ | prop | valor | cuándo | fuente |
31
+ |---|---|---|---|
32
+ | `tabs` | 2 a 4 pestañas | «Todas» primero, luego «No leídas» y los grupos del rol (Pedidos, Promos, Encargos…). Más de 4 no caben en el panel. | propuesta |
33
+ | `tabs[].badge` | número > 0 | El conteo de **no leídas**, solo en la pestaña donde importa. Es un `sui-badge` rojo lleno: un conteo que pide acción. | según el componente · regla de badge: canon |
34
+ | `tabs[].dividerBefore` | `true` | Separa lo que no es bandeja activa: «Archivadas» al final. | según el componente (Figma) |
35
+ | `[(active)]` | el `value` de la pestaña | Siempre enlazado: es un model de dos vías. | según el componente |
36
+ | `showFilter` | `true` (default) | Solo si el panel tiene filtros de verdad (por local, por fecha). | propuesta |
37
+ | `showFilter` | `false` | Lo normal: sin filtros, un botón que no hace nada estorba. | propuesta |
38
+ | `filterLabel` | texto | El nombre del botón de filtro para el lector de pantalla. Default «Filtrar». | según el componente |
39
+
40
+ ## Tamaño en mobile y en web
41
+
42
+ **No tiene `size`**: la fila mide 48 px, pero cada pestaña es un botón de **20 px de alto** (solo el
43
+ texto) y el filtro es un compact-button de 24 px.
44
+
45
+ | uso | mobile | web | fuente |
46
+ |---|---|---|---|
47
+ | las pestañas | **no alcanza**: 20 px de alto para el dedo. Usar `sui-tab-chips` | 20 px de alto, en una fila de 48 | sin tamaño suficiente (no se cambia aquí) |
48
+ | el filtro | 24 px, **bajo el mínimo de 32 px** en mobile | 24 px | sin tamaño suficiente |
49
+
50
+ ```html
51
+ @if (bp.isMobile()) {
52
+ <sui-tab-chips [chips]="pestanas" [value]="activa()" (valueChange)="activa.set($event)" />
53
+ } @else {
54
+ <sui-notification-tab-menu [tabs]="pestanas" [(active)]="activa" [showFilter]="false" />
55
+ }
56
+ ```
57
+
58
+ ## «No usar»: por qué no hoy y cuándo sí
59
+
60
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
61
+ |---|---|---|
62
+ | el componente | no hay panel de campana; la bandeja es una pantalla con `sui-tab-chips` | si el panel del local o el super admin suman la campana con bandeja desplegable en web |
63
+ | `showFilter` | no hay filtros en la bandeja | filtrar avisos por local (central con varios locales) o por fecha |
64
+ | `dividerBefore` | no hay «Archivadas» | si se agrega archivar avisos |
65
+
66
+ ## API
67
+
68
+ <!-- api:inicio -->
69
+ <!-- generada: no editar a mano — `npm run docs` -->
70
+
71
+ | input | tipo | default |
72
+ |---|---|---|
73
+ | `tabs` | `SuiNotificationTab<T>[]` | — (requerido) |
74
+ | `active` · model, `[(active)]` | `T` | `undefined` |
75
+ | `showFilter` | `boolean` | `true` |
76
+ | `filterLabel` | `string` | `'Filtrar'` |
77
+
78
+ | output | payload |
79
+ |---|---|
80
+ | `tabChange` | `T` |
81
+ | `filterClick` | `MouseEvent` |
82
+ <!-- api:fin -->
83
+
84
+ ```ts
85
+ interface SuiNotificationTab<T = string> {
86
+ value: T;
87
+ label: string;
88
+ badge?: number; // > 0 lo muestra
89
+ dividerBefore?: boolean; // línea vertical antes de esta pestaña
90
+ }
91
+ ```
92
+
93
+ ## Trampas
94
+
95
+ - **`showFilter` arranca en `true`**: si no pasas `false`, aparece un botón de filtro.
96
+ - `tabs` es **obligatorio** y `active` es un model: `[(active)]`, no `[value]` como en `sui-tab-chips`.
97
+ - La raya de la pestaña activa usa el primario de la app (naranja), no el azul del Figma.
98
+ - El badge no tiene tope: 120 sale «120». Si hace falta, el texto lo recorta quien lo usa («99+» va
99
+ en `label`, no en `badge`, porque `badge` es un número).
100
+ - El host lleva `role="tablist"` y cada pestaña `role="tab"` con `aria-selected`; no hay panel `tabpanel` asociado: lo arma quien lo usa.
101
+
102
+ ## Ejemplo
103
+
104
+ ```html
105
+ <sui-notification-tab-menu [showFilter]="false" [(active)]="pestana"
106
+ [tabs]="[
107
+ { value: 'todas', label: 'Todas' },
108
+ { value: 'no-leidas', label: 'No leídas', badge: 3 },
109
+ { value: 'pedidos', label: 'Pedidos' },
110
+ { value: 'archivadas', label: 'Archivadas', dividerBefore: true },
111
+ ]" />
112
+ ```
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: sui-otp-input
3
+ selector: sui-otp-input
4
+ resumen: Código de verificación dígito por dígito, una celda por número (SMS, código de entrega)
5
+ alternativas: [sui-input (un código largo o que se pega entero), sui-input type="password-toggle" (una clave que no se ve)]
6
+ ---
7
+
8
+ # sui-otp-input
9
+
10
+ Story: [Molecules/Input/Otp Input](https://sui-design-system.web.app/?path=/docs/molecules-input-otp-input--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para un código corto que llega por otro canal y se copia dígito por dígito: un código
15
+ por SMS o WhatsApp, o un código de entrega que el cliente le dicta al repartidor. Salta solo a la
16
+ celda siguiente, retrocede con borrar, reparte lo que se pega y avisa con `(completed)` cuando
17
+ está lleno. La primera celda pide `autocomplete="one-time-code"` (el teléfono sugiere el código
18
+ del SMS).
19
+
20
+ **No lo uses** para:
21
+
22
+ - Una contraseña o PIN que se elige: `sui-input type="password-toggle"`.
23
+ - Un código de más de 6 caracteres o con letras y guiones (un cupón): `sui-input`.
24
+
25
+ Regla corta: **código corto que llega por otro canal → otp · cupón o clave → input.**
26
+
27
+ ## Qué variante usar
28
+
29
+ > **La guía del DS lo define como el campo para escribir códigos de un solo uso dígito por dígito,
30
+ > pensado para flujos de verificación** con códigos numéricos cortos. Muestra error, deshabilitado
31
+ > y celdas cuadradas. No dice cuántas celdas.
32
+ >
33
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**. Hoy no hay login por SMS ni
34
+ > código de entrega.
35
+
36
+ | prop / evento | cuándo | fuente |
37
+ |---|---|---|
38
+ | `type="number"` (default) | Códigos numéricos: teclado de números. | según la guía del DS (códigos numéricos) |
39
+ | `type="text"` | No usar hoy: un código con letras se tipea mejor en un `sui-input`. | propuesta, falta aprobación |
40
+ | `length` | `6` para un SMS (lo que manda Firebase); `4` para un código de entrega dictado en voz alta. | propuesta, falta aprobación |
41
+ | `error` | El código no coincide. Pinta todas las celdas; el mensaje va aparte, debajo. | según la guía del DS (ejemplo con error) |
42
+ | `disabled` | Mientras se verifica el código. | según la guía del DS (ejemplo deshabilitado) |
43
+ | `ariaLabel` | Qué código es: «Código que te llegó por SMS». El default es «Código de verificación». | propuesta, falta aprobación |
44
+ | `(completed)` | Verificar apenas se llena, sin botón «Continuar». | propuesta, falta aprobación |
45
+
46
+ ## Tamaño en mobile y en web
47
+
48
+ Tiene `size` (extensión SUI) para la celda. `large` es el de la guía y el default: **80 × 64** con 8
49
+ px entre celdas (6 celdas = 520 px). Para el teléfono, **`medium` (52 × 56)**: 6 celdas miden 352
50
+ px y entran en los 358 útiles de una pantalla de 390. `small` (44 × 48, dígito de 20) queda para
51
+ 360 px o un código dentro de una tarjeta.
52
+
53
+ Además, **el componente nunca es más ancho que su contenedor**: si las celdas no entran, se achican
54
+ parejo en vez de salirse de la pantalla (un `large` de 6 en 390 queda en celdas de 53 × 64). Es la red
55
+ de seguridad, no el tamaño a elegir: se ve angosto y alto.
56
+
57
+ | size | celda | 6 celdas | fuente |
58
+ |---|---|---|---|
59
+ | `large` (default) | 80 × 64 · dígito 24 | 520 px | según la guía del DS |
60
+ | `medium` | 52 × 56 · dígito 24 | 352 px | canon (aprobado por el dueño, 03-10-2026), extensión SUI |
61
+ | `small` | 44 × 48 · dígito 20 | 304 px | canon (aprobado por el dueño, 03-10-2026), extensión SUI |
62
+
63
+ | uso | mobile (390) | web | fuente |
64
+ |---|---|---|---|
65
+ | código de 6 dígitos | `medium` | `large` | propuesta, falta aprobación |
66
+ | código de 4 dígitos | `large` (344 px, entra) | `large` | propuesta, falta aprobación |
67
+
68
+ ```html
69
+ <sui-otp-input [length]="6" [size]="bp.isMobile() ? 'medium' : 'large'" [(value)]="codigo" />
70
+ <!-- bp = BreakpointsService -->
71
+ ```
72
+
73
+ ## «No usar»: por qué no hoy y cuándo sí
74
+
75
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
76
+ |---|---|---|
77
+ | el componente entero (0 usos) | no hay verificación por código | verificar el teléfono del cliente por SMS, o un código de entrega de 4 dígitos que el cliente le dicta al repartidor |
78
+ | `type="text"` | los códigos que llegan son numéricos | un código alfanumérico corto de una campaña |
79
+ | `size="small"` | `medium` ya entra en 390 | pantallas de 360 px o un código dentro de una tarjeta |
80
+
81
+ ## API
82
+
83
+ <!-- api:inicio -->
84
+ <!-- generada: no editar a mano — `npm run docs` -->
85
+
86
+ | input | tipo | default |
87
+ |---|---|---|
88
+ | `length` | `number` | `6` |
89
+ | `size` | `'large' \| 'medium' \| 'small'` | `'large'` |
90
+ | `type` | `'number' \| 'text'` | `'number'` |
91
+ | `disabled` · model, `[(disabled)]` | `boolean` | `false` |
92
+ | `error` | `boolean` | `false` |
93
+ | `ariaLabel` | `string` | `'Código de verificación'` |
94
+ | `value` · model, `[(value)]` | `string` | `''` |
95
+
96
+ | output | payload |
97
+ |---|---|
98
+ | `completed` | `string` |
99
+ <!-- api:fin -->
100
+
101
+ `value` y `disabled` son `model`. Implementa `ControlValueAccessor`.
102
+
103
+ ## Trampas
104
+
105
+ - **6 celdas `large` no entran en un teléfono** (520 px): se achican solas, pero en mobile va
106
+ `size="medium"`. Ver «Tamaño en mobile y en web».
107
+ - `error` no trae mensaje: el texto va debajo, con `sui-form-hint` en variante error.
108
+ - `disabled` es un `model`: si el formulario lo deshabilita, el componente lo escribe de vuelta.
109
+ - No hay grupos (3-3): si se necesitan, son dos componentes.
110
+
111
+ ## Ejemplo
112
+
113
+ ```html
114
+ <sui-otp-input [length]="4" ariaLabel="Código de entrega" [error]="codigoMalo()"
115
+ [(value)]="codigo" (completed)="verificar($event)" />
116
+ ```
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: sui-pagination
3
+ selector: sui-pagination
4
+ resumen: Números de página para recorrer una tabla larga en web cuando todos los datos ya están cargados
5
+ alternativas: [scroll infinito (directiva appInfiniteScroll de la app, para listas de Firestore), sui-stepper (pasos de un flujo), sui-tab-chips (filtrar en vez de paginar)]
6
+ ---
7
+
8
+ # sui-pagination
9
+
10
+ Story: [Molecules/Pagination](https://sui-design-system.web.app/?path=/docs/molecules-pagination--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** en **web**, debajo de una **tabla larga cuyos datos ya están en memoria** (un reporte, un
15
+ export, una tabla filtrada en el navegador), cuando ayuda saber «voy por la 3 de 12» o saltar a una
16
+ página.
17
+
18
+ **No lo uses** para:
19
+
20
+ - **Mobile**: las listas se recorren con el dedo. Va scroll infinito (la directiva
21
+ `appInfiniteScroll` que ya usan productos, clientes y equipo).
22
+ - **Listas que se leen de Firestore por cursor** (`getCollectionPage`): con cursores solo se puede
23
+ pedir la página siguiente. Saltar a la página 7 obliga a leer las 6 anteriores, y Firestore las
24
+ cobra igual. El canon de la app para esas listas es scroll infinito (CLAUDE.md, «Performance —
25
+ Firestore reads»).
26
+ - Pasos de un flujo: `sui-stepper`.
27
+ - Pocas filas (una sola página): no se muestra.
28
+
29
+ Regla corta: **tabla con todo cargado en web → pagination · lista de Firestore o mobile → scroll infinito.**
30
+
31
+ ## Qué variante usar
32
+
33
+ > **Lo que dice la guía del DS**: es un conjunto horizontal de enlaces para **navegar contenido
34
+ > paginado**. Trae tres formas: `basic`, `rounded` (en SUI, `full-radius`) y `group`, más botones
35
+ > de primera/última y anterior/siguiente, y la página actual marcada. No trae do/don't ni dice
36
+ > cuándo cada forma.
37
+ >
38
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-badge`): **0 usos**.
39
+ > Las 3 listas largas de la app (`pages/products/products.html:242`, `pages/customers/customers.html:161`,
40
+ > `pages/settings/team/team.ts:347`) usan scroll infinito.
41
+
42
+ ### `type`
43
+
44
+ | type | cuándo | fuente |
45
+ |---|---|---|
46
+ | `basic` (default) | El normal, debajo de una tabla con bordes. | según la guía del DS (es su default); el cuándo es propuesta |
47
+ | `group` | Cuando la tabla va dentro de una card y la paginación se apoya en su borde: un solo bloque se lee como parte de la tabla. | según la guía del DS (la forma existe); el cuándo es propuesta |
48
+ | `full-radius` | No: el resto de los controles de la app (botones, inputs) tienen esquinas de 8-10, no círculos. | según la guía del DS (la forma existe); el no es propuesta |
49
+
50
+ ### `size`
51
+
52
+ | size | qué mide | cuándo | fuente |
53
+ |---|---|---|---|
54
+ | `medium` (default) | celdas de 32 px | Web, debajo de una tabla. | extensión de SUI (la guía tiene un tamaño); el cuándo es propuesta |
55
+ | `small` | celdas de 28 px | Web, en una tabla dentro de un modal o panel angosto. | propuesta, falta aprobación |
56
+
57
+ ### Botones y texto
58
+
59
+ | input | valor | cuándo | fuente |
60
+ |---|---|---|---|
61
+ | `nextPrevious` | `true` (default) | Siempre. | según la guía del DS |
62
+ | `firstLast` | `false` (default) | Lo normal: con las elipsis ya se ve la primera y la última. | propuesta, falta aprobación |
63
+ | `firstLast` | `true` | Más de 20 páginas, donde ir al final es un caso de uso (la última semana de un export). | según la guía del DS (los botones existen); el cuándo es propuesta |
64
+ | `siblingCount` | `1` (default) | Siempre. Más números a cada lado ensanchan sin ayudar. | propuesta, falta aprobación |
65
+ | `showInfo` | `true` (default) | Con `infoFormat="items"`: «Mostrando 11-20 de 134». | propuesta, falta aprobación |
66
+ | `infoFormat` | `'items'` (default) | El único en español. | según el código |
67
+ | `infoFormat` | `'pages'` | Imprime «Página 2 de 14» (en español desde 03-10-2026). Cuando no se sabe el total de ítems o no importa. | propuesta, falta aprobación |
68
+ | `pageSizeOptions` | `[]` (default) | Lo normal: sin selector de filas por página. | propuesta, falta aprobación |
69
+ | `pageSizeOptions` | `[10, 25, 50]` | Las opciones dicen «10 / página» (en español desde 03-10-2026). | propuesta, falta aprobación |
70
+
71
+ Usos de hoy que rompen las reglas: **0** (no se usa).
72
+
73
+ ## Tamaño en mobile y en web
74
+
75
+ **No tiene tamaño para mobile**: la celda más grande mide 32 px, por debajo de los 44 tocables. En
76
+ mobile no se usa.
77
+
78
+ | uso | mobile | web | fuente |
79
+ |---|---|---|---|
80
+ | debajo de una tabla | no se usa: scroll infinito | `medium` (32) | propuesta, falta aprobación |
81
+ | tabla en un panel angosto | no se usa | `small` (28) | propuesta, falta aprobación |
82
+
83
+ ```html
84
+ @if (!bp.isMobile()) {
85
+ <sui-pagination [total]="filas().length" [(currentPage)]="pagina" [pageSize]="25" />
86
+ }
87
+ ```
88
+
89
+ ## «No usar»: por qué no hoy y cuándo sí
90
+
91
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
92
+ |---|---|---|
93
+ | el componente entero | las listas de la app leen de Firestore por cursor y usan scroll infinito | una tabla de web con todos los datos ya cargados (un reporte de 300 filas filtrado en el navegador) |
94
+ | `full-radius` | no combina con las esquinas del resto de la app | nunca, salvo que la app pase a botones redondos |
95
+ | `infoFormat="pages"` | sale en inglés | cuando el DS lo traduzca («Página 2 de 14») |
96
+ | `firstLast` | ninguna tabla tiene 20+ páginas | un export largo donde ir al final es habitual |
97
+
98
+ ## API
99
+
100
+ <!-- api:inicio -->
101
+ <!-- generada: no editar a mano — `npm run docs` -->
102
+
103
+ | input | tipo | default |
104
+ |---|---|---|
105
+ | `total` | `number` | — (requerido) |
106
+ | `pageSize` · model, `[(pageSize)]` | `number` | `10` |
107
+ | `siblingCount` | `number` | `1` |
108
+ | `showInfo` | `boolean` | `true` |
109
+ | `size` | `'small' \| 'medium'` | `'medium'` |
110
+ | `type` | `'basic' \| 'full-radius' \| 'group'` | `'basic'` |
111
+ | `firstLast` | `boolean` | `false` |
112
+ | `nextPrevious` | `boolean` | `true` |
113
+ | `infoFormat` | `'items' \| 'pages'` | `'items'` |
114
+ | `pageSizeOptions` | `readonly number[]` | `[]` |
115
+ | `currentPage` · model, `[(currentPage)]` | `number` | `1` |
116
+
117
+ | output | payload |
118
+ |---|---|
119
+ | `pageChange` | `number` |
120
+ | `pageSizeChange` | `number` |
121
+ <!-- api:fin -->
122
+
123
+ ```ts
124
+ type SuiPaginationType = 'basic' | 'full-radius' | 'group';
125
+ type SuiPaginationSize = 'small' | 'medium';
126
+ type SuiPaginationInfoFormat = 'items' | 'pages';
127
+ ```
128
+
129
+ ## Trampas
130
+
131
+ - **`total` es la cantidad de ítems, no de páginas.** Las páginas salen de `total / pageSize`.
132
+ - **`currentPage` arranca en 1** (el stepper arranca en 0).
133
+ - **`currentPage` y `pageSize` son `model`**: con `[(currentPage)]` no hace falta `(pageChange)`.
134
+ - **No carga datos**: solo dice qué página se pidió. Cortar las filas lo hace la pantalla.
135
+ - **Textos en español** desde 03-10-2026: «Página X de Y» (`infoFormat="pages"`), «N / página»
136
+ (`pageSizeOptions`) y el default «Mostrando X-Y de Z».
137
+ - **La página actual se pinta con `bg-weak-50`**, el mismo gris de las superficies de fondo: sobre
138
+ una card gris o un fondo `weak-50` no se distingue cuál es. Ponla sobre blanco.
139
+ - **Con `total` en 0 no dibuja el texto**, pero sí los botones (una sola página «1»). Ocúltalo tú.
140
+ - Las flechas son `<i class="ri-…">`: necesitan la fuente de Remix Icon cargada.
141
+
142
+ ## Ejemplo
143
+
144
+ ```html
145
+ <sui-pagination [total]="134" [pageSize]="10" [(currentPage)]="pagina" type="group" />
146
+ ```