@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.
Files changed (85) hide show
  1. package/docs/INDEX.md +75 -80
  2. package/docs/sui-accordion/README.md +171 -0
  3. package/docs/sui-activity-feed/README.md +155 -0
  4. package/docs/sui-activity-filter/README.md +100 -0
  5. package/docs/sui-alert/README.md +13 -0
  6. package/docs/sui-badge/README.md +17 -0
  7. package/docs/sui-banner/README.md +190 -0
  8. package/docs/sui-breadcrumb-item/README.md +96 -0
  9. package/docs/sui-breadcrumbs/README.md +122 -0
  10. package/docs/sui-button/README.md +36 -0
  11. package/docs/sui-card/README.md +168 -0
  12. package/docs/sui-chart-legend/README.md +140 -0
  13. package/docs/sui-chart-legend-dot/README.md +118 -0
  14. package/docs/sui-checkbox-card/README.md +150 -0
  15. package/docs/sui-command-menu-footer/README.md +100 -0
  16. package/docs/sui-command-menu-item/README.md +140 -0
  17. package/docs/sui-command-menu-search/README.md +105 -0
  18. package/docs/sui-compact-select/README.md +130 -0
  19. package/docs/sui-compact-select-for-input/README.md +120 -0
  20. package/docs/sui-content-card/README.md +147 -0
  21. package/docs/sui-content-label/README.md +148 -0
  22. package/docs/sui-counter-input/README.md +148 -0
  23. package/docs/sui-date-selector/README.md +98 -0
  24. package/docs/sui-datepicker/README.md +197 -0
  25. package/docs/sui-day-cell/README.md +118 -0
  26. package/docs/sui-day-label/README.md +81 -0
  27. package/docs/sui-drawer-footer/README.md +127 -0
  28. package/docs/sui-drawer-header/README.md +140 -0
  29. package/docs/sui-dropdown-caption/README.md +92 -0
  30. package/docs/sui-dropdown-item/README.md +163 -0
  31. package/docs/sui-dropdown-menu/README.md +196 -0
  32. package/docs/sui-dropdown-search/README.md +113 -0
  33. package/docs/sui-empty-state/README.md +145 -0
  34. package/docs/sui-file-upload/README.md +154 -0
  35. package/docs/sui-file-upload-card/README.md +129 -0
  36. package/docs/sui-filter/README.md +181 -0
  37. package/docs/sui-href/README.md +85 -0
  38. package/docs/sui-image-upload/README.md +200 -0
  39. package/docs/sui-image-upload-profile/README.md +145 -0
  40. package/docs/sui-img-retry/README.md +107 -0
  41. package/docs/sui-inline-input/README.md +104 -0
  42. package/docs/sui-inline-select/README.md +101 -0
  43. package/docs/sui-input/README.md +247 -0
  44. package/docs/sui-integration-switch/README.md +142 -0
  45. package/docs/sui-modal/README.md +238 -0
  46. package/docs/sui-modal-footer/README.md +178 -0
  47. package/docs/sui-modal-header/README.md +115 -0
  48. package/docs/sui-modal-sheet/README.md +170 -0
  49. package/docs/sui-notification/README.md +179 -0
  50. package/docs/sui-notification-footer/README.md +98 -0
  51. package/docs/sui-notification-tab-menu/README.md +112 -0
  52. package/docs/sui-otp-input/README.md +116 -0
  53. package/docs/sui-page-header/README.md +123 -0
  54. package/docs/sui-pagination/README.md +146 -0
  55. package/docs/sui-password-strength/README.md +99 -0
  56. package/docs/sui-period-range/README.md +93 -0
  57. package/docs/sui-popover/README.md +178 -0
  58. package/docs/sui-product-card/README.md +173 -0
  59. package/docs/sui-radio-card/README.md +160 -0
  60. package/docs/sui-rating/README.md +7 -6
  61. package/docs/sui-rich-editor/README.md +114 -0
  62. package/docs/sui-segmented-control/README.md +3 -0
  63. package/docs/sui-select/README.md +183 -0
  64. package/docs/sui-sidebar/README.md +217 -0
  65. package/docs/sui-stepper/README.md +166 -0
  66. package/docs/sui-store-card/README.md +147 -0
  67. package/docs/sui-switch-card/README.md +149 -0
  68. package/docs/sui-switch-label/README.md +3 -0
  69. package/docs/sui-tab-chips/README.md +188 -0
  70. package/docs/sui-tab-menu-horizontal/README.md +142 -0
  71. package/docs/sui-tab-menu-vertical/README.md +136 -0
  72. package/docs/sui-table/README.md +264 -0
  73. package/docs/sui-tag-input/README.md +123 -0
  74. package/docs/sui-textarea/README.md +160 -0
  75. package/docs/sui-time-picker/README.md +240 -0
  76. package/docs/sui-toast/README.md +190 -0
  77. package/docs/sui-tooltip/README.md +201 -0
  78. package/docs/sui-topbar/README.md +153 -0
  79. package/docs/sui-topbar-button/README.md +95 -0
  80. package/docs/sui-topbar-item/README.md +97 -0
  81. package/docs/sui-topbar-user/README.md +83 -0
  82. package/fesm2022/ajuarezso-sui-design-system.mjs +291 -246
  83. package/fesm2022/ajuarezso-sui-design-system.mjs.map +1 -1
  84. package/package.json +1 -1
  85. package/types/ajuarezso-sui-design-system.d.ts +208 -50
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: sui-tab-menu-horizontal
3
+ selector: sui-tab-menu-horizontal
4
+ resumen: Pestañas subrayadas para cambiar entre secciones de contenido distinto dentro de una pantalla
5
+ alternativas: [sui-tab-chips (filtros o categorías que se deslizan), sui-segmented-control (2 a 4 modos de la misma vista), sui-tab-menu-vertical (menú lateral de ajustes en web)]
6
+ ---
7
+
8
+ # sui-tab-menu-horizontal
9
+
10
+ Story: [Molecules/Tabs/Tab Menu Horizontal](https://sui-design-system.web.app/?path=/docs/molecules-tabs-tab-menu-horizontal--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para cambiar entre **secciones de contenido distinto** de la misma pantalla o detalle:
15
+ «Opiniones · Información» en el modal del local, «Ventas · Productos · Repartidores · Clientes» en
16
+ reportes. Cada pestaña muestra otra cosa, no la misma lista recortada.
17
+
18
+ **No lo uses** para:
19
+
20
+ - **Filtrar una lista** o saltar entre categorías: `sui-tab-chips`.
21
+ - **2 a 4 modos de la misma vista** (cuadrícula o lista): `sui-segmented-control`.
22
+ - **Secciones que no entran a lo ancho en mobile** (4 con nombres largos o con `number`, o 5+): el
23
+ componente no se desliza y las pestañas se salen de la pantalla (ver Trampas). Ahí, por ahora,
24
+ `sui-tab-chips`.
25
+ - Un menú lateral de ajustes en web: `sui-tab-menu-vertical`.
26
+
27
+ Regla corta (README de `sui-segmented-control`, 03-10-2026): **2-4 modos de la misma vista →
28
+ segmented · secciones → tab menu · filtros → tab-chips.**
29
+
30
+ ## Qué variante usar
31
+
32
+ > **Lo que dice la guía del DS**: es un layout lineal para **cambiar entre secciones o categorías**.
33
+ > Muestra un ejemplo con pestañas que **desbordan y se deslizan**, y su pestaña mide 48 px de alto
34
+ > (toda la franja es tocable). No trae do/don't ni tamaños.
35
+ >
36
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-badge`):
37
+ > **1 uso**: `components/store-ratings-modal/store-ratings-modal.html:43` («Opiniones ·
38
+ > Información»), sin `size`, sin ícono, sin `number`.
39
+
40
+ ### Ítems (`tabs`)
41
+
42
+ | qué | cuándo | fuente |
43
+ |---|---|---|
44
+ | texto | Lo normal: el nombre de la sección, una o dos palabras. | según la guía del DS (ejemplos con texto) |
45
+ | `icon` | Solo si **todas** las pestañas llevan ícono y ayuda a reconocerlas. Nunca en una sola. | propuesta, falta aprobación |
46
+ | `number` | Pendientes **que piden acción** dentro de esa sección («Solicitudes 3»). Es un círculo rojo: no lo uses para totales neutros. | propuesta, falta aprobación |
47
+ | `rightIcon` | No: la flecha dice «te lleva a otra pantalla» y una pestaña no navega. | propuesta, falta aprobación |
48
+ | `disabled` | Una sección que existe pero no aplica todavía (depende del plan). | propuesta, falta aprobación |
49
+
50
+ ### `size`
51
+
52
+ | size | cuándo | fuente |
53
+ |---|---|---|
54
+ | `medium` (default) | El único que cambia algo: **ninguno**. El input existe pero no tiene estilos; los cuatro valores se ven igual. No lo pases. | según el código (`sui-tab-menu-horizontal.scss`: no hay reglas por tamaño) |
55
+ | `small` · `large` · `xlarge` | No usar: no hacen nada. | según el código |
56
+
57
+ **Usos de hoy que rompen las reglas:**
58
+
59
+ - **0** en este componente. Los casos que deberían ser tab menu y hoy son `sui-tab-chips` están en el
60
+ README de `sui-tab-chips` (4: `pages/products/products.html:6`, `pages/promociones/promociones.html:7`,
61
+ `pages/super-admin/master-drivers/master-drivers.html:16`, `pages/reports/reports.html:98`).
62
+ - **A verificar en mobile:** el uso actual es del modal del local, que el cliente abre en el
63
+ teléfono. La pestaña mide 20 px de alto tocable (ver Tamaño).
64
+
65
+ ## Tamaño en mobile y en web
66
+
67
+ Cada pestaña ocupa **todo el alto de la franja**, así que lo tocable es la franja entera (desde
68
+ 03-10-2026; antes eran solo los 20 px del texto). El `size` cambia ese alto y el texto:
69
+
70
+ | size | franja tocable (+ 2 de bordes) | texto | ícono | gap | fuente |
71
+ |---|---|---|---|---|---|
72
+ | `small` | 40 | `label-sm` 14/20 | 20 | 20 | canon (aprobado por el dueño, 03-10-2026): extensión SUI |
73
+ | `medium` (default) | 48 | `label-sm` 14/20 | 20 | 24 | Figma |
74
+ | `large` | 52 | `label-md` 16/24 | 20 | 24 | canon (aprobado por el dueño, 03-10-2026): extensión SUI |
75
+ | `xlarge` | 56 | `label-md` 16/24 | 24 | 28 | canon (aprobado por el dueño, 03-10-2026): extensión SUI |
76
+
77
+ | uso | mobile | web | fuente |
78
+ |---|---|---|---|
79
+ | 2-3 secciones | `large` (52, texto 16) | `medium` (48) | propuesta: el tamaño es extensión SUI aprobada (03-10-2026); el «cuándo» falta aprobarlo |
80
+ | 4+ secciones | si no entran a 390 px, se salen sin poder deslizarse (las 4 del panel de repartidores, con sus números, ya no entran a 360): va `sui-tab-chips` | el mismo | según el código |
81
+
82
+ ```html
83
+ <sui-tab-menu-horizontal [tabs]="secciones" [(active)]="seccion" [size]="bp.isMobile() ? 'large' : 'medium'" />
84
+ ```
85
+
86
+ ## «No usar»: por qué no hoy y cuándo sí
87
+
88
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
89
+ |---|---|---|
90
+ | `rightIcon` | una pestaña no lleva a otra pantalla | nunca en pestañas; es del tab menu vertical cuando cada ítem abre una página |
91
+ | `icon` | ningún caso de hoy lo necesita | secciones con ícono reconocible en las tres (Mapa · Lista · Fotos) en web |
92
+ | `number` | ninguna sección del único uso tiene pendientes | «Solicitudes» o «Recargas» del panel de repartidores, si se pasa a tab menu |
93
+ | `disabled` | ninguna sección bloqueada | una sección que depende del plan («Reportes» en plan básico) |
94
+
95
+ ## API
96
+
97
+ <!-- api:inicio -->
98
+ <!-- generada: no editar a mano — `npm run docs` -->
99
+
100
+ | input | tipo | default |
101
+ |---|---|---|
102
+ | `tabs` | `SuiTabMenuHorizontalItem<T>[]` | — (requerido) |
103
+ | `active` · model, `[(active)]` | `T` | `undefined` |
104
+ | `size` | `'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` |
105
+
106
+ | output | payload |
107
+ |---|---|
108
+ | `tabChange` | `T` |
109
+ <!-- api:fin -->
110
+
111
+ ```ts
112
+ interface SuiTabMenuHorizontalItem<T = string> {
113
+ value: T;
114
+ label: string;
115
+ icon?: string; // RemixIcon, 20 px
116
+ rightIcon?: boolean; // flecha en píldora
117
+ number?: number; // círculo rojo; 0/undefined no se dibuja
118
+ disabled?: boolean;
119
+ }
120
+ ```
121
+
122
+ ## Trampas
123
+
124
+ - **Sin `active` no se marca ninguna pestaña.** Pásale siempre un valor inicial.
125
+ - **No se desliza.** El track no tiene `overflow-x`: con muchas pestañas en un ancho chico se salen
126
+ del contenedor. La guía del DS sí lo resuelve; SUI todavía no.
127
+ - **El área tocable es toda la franja** (40–56 según `size`), desde 03-10-2026: el aire de 14 px
128
+ que Figma pone en el contenedor ahora va dentro de cada pestaña. Si algo de afuera le pone
129
+ padding al `.sui-tab-menu-horizontal__track`, la franja crece al doble.
130
+ - **Emite dos veces**: `tabChange` y `activeChange`. Con `[(active)]` no hace falta `(tabChange)`.
131
+ - **Trae bordes arriba y abajo** (`stroke-soft-200`) y fondo blanco: no lo metas en una card que ya
132
+ tiene borde, o quedan dos líneas.
133
+ - El ícono de la pestaña activa es `strong-950`, no naranja; en el tab menu vertical es naranja.
134
+ Es a propósito.
135
+
136
+ ## Ejemplo
137
+
138
+ ```html
139
+ <sui-tab-menu-horizontal
140
+ [tabs]="[{ value: 'opiniones', label: 'Opiniones' }, { value: 'info', label: 'Información' }]"
141
+ [(active)]="tab" />
142
+ ```
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: sui-tab-menu-vertical
3
+ selector: sui-tab-menu-vertical
4
+ resumen: Menú apilado para cambiar entre secciones de una página de ajustes en web
5
+ alternativas: [sui-tab-menu-horizontal (pocas secciones arriba del contenido), sui-sidebar (navegación de toda la app), sui-tab-chips (filtros o categorías)]
6
+ ---
7
+
8
+ # sui-tab-menu-vertical
9
+
10
+ Story: [Molecules/Tabs/Tab Menu Vertical](https://sui-design-system.web.app/?path=/docs/molecules-tabs-tab-menu-vertical--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** en **web** para una página con varias secciones que se leen de a una, con el menú a la
15
+ izquierda y el contenido a la derecha: los ajustes del local («Datos del local · Horario · Métodos
16
+ de pago · Repartidores»), el perfil del super admin.
17
+
18
+ **No lo uses** para:
19
+
20
+ - **Mobile**: el ítem mide 36 px y ocupa el ancho de la pantalla dejando el contenido debajo. En
21
+ mobile cada sección es una fila de una lista que abre su propia pantalla.
22
+ - La navegación de toda la app: `sui-sidebar`.
23
+ - 2 a 4 secciones arriba del contenido: `sui-tab-menu-horizontal`.
24
+ - Filtros de una lista: `sui-tab-chips`.
25
+
26
+ Regla corta: **secciones de una página de ajustes en web → tab menu vertical · en mobile → lista de
27
+ filas que abren pantalla.**
28
+
29
+ ## Qué variante usar
30
+
31
+ > **Lo que dice la guía del DS**: es un layout apilado para **cambiar entre secciones o categorías**.
32
+ > Sus ejemplos son ajustes («Profile Settings · Contact Information · Social Links · Export Data»),
33
+ > uno con **título arriba** (en SUI, `dividerLabel`) y otro con **contenedor con estilo** (en SUI,
34
+ > `menuStyle="card"`). No trae do/don't.
35
+ >
36
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-badge`): **0 usos**.
37
+
38
+ ### `menuStyle`
39
+
40
+ | valor | cuándo | fuente |
41
+ |---|---|---|
42
+ | `card` | El menú flota junto al contenido sobre un fondo gris (`bg-weak-50`): el borde y la sombra lo separan. | según la guía del DS (ejemplo «contenedor con estilo»); el cuándo es propuesta |
43
+ | `list` | El menú vive dentro de una columna que ya tiene fondo o borde propio (una card, un panel). | según la guía del DS (ejemplo base); el cuándo es propuesta |
44
+
45
+ ### `dividerLabel`
46
+
47
+ | valor | cuándo | fuente |
48
+ |---|---|---|
49
+ | vacío (default) | Un solo grupo de secciones. | propuesta, falta aprobación |
50
+ | texto | Nombra el grupo cuando hay más de un menú apilado («Tu local» · «Tu cuenta»). Sale en MAYÚSCULAS y gris. | según la guía del DS (ejemplo con título) |
51
+
52
+ ### Ítems (`tabs`)
53
+
54
+ | qué | cuándo | fuente |
55
+ |---|---|---|
56
+ | texto | El nombre de la sección. Una o dos palabras; si es más largo, se corta con «…». | según la guía del DS (ejemplos con texto) |
57
+ | `icon` | Sí, en todos los ítems: en un menú lateral el ícono ayuda a encontrar rápido. El activo se pinta naranja. | propuesta, falta aprobación |
58
+ | `number` | Pendientes que piden acción en esa sección (círculo rojo). | propuesta, falta aprobación |
59
+ | `rightIcon` | Cuando el ítem lleva a otra página en vez de cambiar el panel de al lado. | propuesta, falta aprobación |
60
+ | `disabled` | Una sección que depende del plan. | propuesta, falta aprobación |
61
+
62
+ Usos de hoy que rompen las reglas: **0** (no se usa).
63
+
64
+ ## Tamaño en mobile y en web
65
+
66
+ Ítem de **36 px** (8 + 20 + 8), ícono de 20. **No tiene `size`** y no tiene tamaño para mobile.
67
+
68
+ | uso | mobile | web | fuente |
69
+ |---|---|---|---|
70
+ | secciones de ajustes | no se usa: lista de filas que abren pantalla | `tab-menu-vertical` | propuesta, falta aprobación |
71
+
72
+ ```html
73
+ @if (!bp.isMobile()) {
74
+ <sui-tab-menu-vertical [tabs]="secciones" [(active)]="seccion" menuStyle="card" />
75
+ }
76
+ ```
77
+
78
+ ## «No usar»: por qué no hoy y cuándo sí
79
+
80
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
81
+ |---|---|---|
82
+ | el componente entero | ninguna pantalla de la app tiene hoy un menú lateral de secciones | una página de ajustes en web con 5+ secciones (ajustes del local en desktop) |
83
+ | en mobile | 36 px y sin lugar para el contenido al lado | nunca |
84
+ | `rightIcon` | ningún menú mezcla paneles y páginas | un ítem «Facturación» que sale a otra página mientras el resto cambia el panel |
85
+
86
+ ## API
87
+
88
+ <!-- api:inicio -->
89
+ <!-- generada: no editar a mano — `npm run docs` -->
90
+
91
+ | input | tipo | default |
92
+ |---|---|---|
93
+ | `tabs` | `SuiTabMenuVerticalItem<T>[]` | — (requerido) |
94
+ | `active` · model, `[(active)]` | `T` | `undefined` |
95
+ | `menuStyle` | `'card' \| 'list'` | `'card'` |
96
+ | `dividerLabel` | `string` | `''` |
97
+
98
+ | output | payload |
99
+ |---|---|
100
+ | `tabChange` | `T` |
101
+ <!-- api:fin -->
102
+
103
+ ```ts
104
+ interface SuiTabMenuVerticalItem<T = string> {
105
+ value: T;
106
+ label: string;
107
+ icon?: string; // RemixIcon, 20 px
108
+ rightIcon?: boolean; // flecha en píldora con sombra
109
+ number?: number; // círculo rojo; 0/undefined no se dibuja
110
+ disabled?: boolean;
111
+ }
112
+ type SuiTabMenuVerticalStyle = 'card' | 'list';
113
+ ```
114
+
115
+ ## Trampas
116
+
117
+ - **El estilo se llama `menuStyle`**, no `style` ni `variant` (ni `verticalStyle`, que era el nombre
118
+ viejo de `sui-tabs`).
119
+ - **Sin `active` no se marca ningún ítem.** Pásale un valor inicial.
120
+ - **Ocupa el ancho del padre**: dale un ancho fijo a la columna (240-280 px).
121
+ - **El ícono activo es naranja** (`primary-base`); en el tab menu horizontal es negro. Es a propósito.
122
+ - **Emite dos veces**: `tabChange` y `activeChange`.
123
+ - `role="tablist"`: si cada ítem navega a una ruta, el lector de pantalla lo anuncia mal; para
124
+ navegación entre rutas, `sui-sidebar`.
125
+
126
+ ## Ejemplo
127
+
128
+ ```html
129
+ <sui-tab-menu-vertical
130
+ menuStyle="card"
131
+ dividerLabel="Tu local"
132
+ [tabs]="[{ value: 'datos', label: 'Datos del local', icon: 'store-2-line' },
133
+ { value: 'horario', label: 'Horario', icon: 'time-line' },
134
+ { value: 'pagos', label: 'Métodos de pago', icon: 'bank-card-line' }]"
135
+ [(active)]="seccion" />
136
+ ```
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: sui-table
3
+ selector: sui-table
4
+ resumen: Tabla de datos — una fila por registro, una columna por dato — para comparar y recorrer listas en web
5
+ alternativas: [tarjetas en grilla con sui-card (la misma lista cuando no entran las columnas), sui-empty-state (no hay nada que listar todavía), sui-pagination (paginar por número de página, debajo de la tabla), sui-activity-feed (historial de quién hizo qué)]
6
+ ---
7
+
8
+ # sui-table
9
+
10
+ Story: [Organisms/Table](https://sui-design-system.web.app/?path=/docs/organisms-table--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** cuando la persona tiene que **comparar varios registros por las mismas columnas**:
15
+ pedidos, repartidores, gastos, recargas. La tabla sirve si leer en vertical una columna
16
+ («¿cuál es el monto más alto?», «¿quién no tiene repartidor?») es parte de la decisión.
17
+
18
+ **No lo uses** para:
19
+
20
+ - Una lista donde cada registro se lee solo (sin comparar con el de al lado): tarjetas.
21
+ - Pantallas del cliente o del repartidor en el teléfono: ahí la lista va en tarjetas o
22
+ filas de una columna. La tabla es de las pantallas de gestión (empresa, super admin, central).
23
+ - Un historial de acciones con autor y hora: `sui-activity-feed`.
24
+ - «Todavía no hay nada» a página completa: `sui-empty-state`. La tabla tiene su propio
25
+ vacío (`emptyMessage`), que es otro registro (ver abajo).
26
+
27
+ Regla corta: **comparar por columnas → tabla · leer de a uno → tarjetas · historial → activity feed.**
28
+
29
+ ## Qué variante usar
30
+
31
+ > **La guía del DS casi no dice cuándo ni cómo usar la tabla.** Su página la define como «una
32
+ > estructura de dos dimensiones donde cada fila es un ítem y cada columna un dato del ítem»,
33
+ > muestra un solo ejemplo (un directorio de personas **con selección por checkbox**), le pone
34
+ > `overflow-x-auto` al contenedor y deriva el orden, la selección y la paginación a una
35
+ > librería externa. No trae do/don't, tamaños ni comportamiento en mobile. Casi todo lo de
36
+ > abajo es **práctica de la app** o **propuesta, falta aprobación**; lo marcado «canon» sale de
37
+ > `master-pedidos/CLAUDE.md` o de `docs/CANON-SUI.md`.
38
+ >
39
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **9 tablas** en 7 pantallas, sin contar
40
+ > prototipos ni vitrinas (`master-preview`, `master-prototipos`, `mockup-sheets-*`, `dx/*`,
41
+ > `master-rutina`, `master-medico`, `dev-index`). `sortable`, `selectable` y `striped`: 0 usos.
42
+ > `sticky`: 1, en una vitrina.
43
+
44
+ ### Lo que sí dice la guía del DS
45
+
46
+ - **Qué es**: filas = ítems, columnas = datos del ítem.
47
+ - **Partes**: cabecera, filas con hover, celdas con esquinas redondeadas y un **divisor entre
48
+ filas**. SUI lo replica (header gris `bg-weak-50`, filas con radio 12 y divisor de 1 px).
49
+ - **Ancho**: el contenedor scrollea en horizontal (`overflow-x-auto`). En SUI eso pasa solo
50
+ **hasta 767 px** o con una columna `sticky` (ver Trampas).
51
+ - **Selección**: su único ejemplo trae checkbox por fila. En SUI es `selectable`.
52
+
53
+ ### Las columnas (`SuiTableColumn`)
54
+
55
+ | prop de columna | cuándo | ejemplo real de la app | fuente |
56
+ |---|---|---|---|
57
+ | `priority: 'leading'` + `descriptionKey` | La **primera** columna: el dato que identifica la fila arriba (14/500) y un dato de apoyo debajo (12/400). Se usa en vez de una plantilla propia para la columna principal. | `pages/orders/orders.ts:278` (pedido + «hace 12 min») · `pages/super-admin/master-orders/master-orders.ts:390` | práctica de la app (7 de 9 tablas) |
58
+ | `priority: 'passive'` | Datos de contexto que no deciden nada: fecha, número de comprobante. Se leen en gris (`text-sub-600`). | `pages/super-admin/master-overview/master-overview.ts:192` | propuesta, falta aprobación |
59
+ | `priority: 'none'` | Columna de acciones (íconos) sin texto propio. Con `label: ''` y `width` fijo. | `pages/super-admin/master-gastos/master-gastos.ts:143` | práctica de la app |
60
+ | `align: 'right'` | Montos. Así se comparan en vertical. | `pages/central/central-gastos/central-gastos.ts:82` | propuesta, falta aprobación |
61
+ | `width` | Columnas de largo fijo: monto, estado, fecha, acciones. | `central-gastos.ts:82-83` | práctica de la app |
62
+ | `minWidth` | Columnas de texto que crecen (nombre, concepto): el piso para que no se aplasten. | `master-orders.ts:392` | práctica de la app |
63
+ | `maxWidth` + `truncate` | Nombres que pueden ser muy largos: corta con «…» y el valor completo sale en tooltip. | `master-overview.ts:193` | práctica de la app |
64
+
65
+ **Una ID nunca es el título de la fila** (`CLAUDE.md`, «Pasada de UX/UI»): la columna `leading`
66
+ lleva el nombre o el número legible del pedido, no el uid.
67
+
68
+ ### Plantillas por columna (`suiTableCell`)
69
+
70
+ Si una celda necesita algo más que texto, se proyecta un `<ng-template suiTableCell="<key>" let-row>`.
71
+ Dentro va un componente SUI, no HTML a mano:
72
+
73
+ | contenido | componente | fuente |
74
+ |---|---|---|
75
+ | estado o categoría | `<sui-badge variant="lighter" size="small">` | canon (CANON-SUI §0: «Chip de estado») |
76
+ | acciones de la fila | `<sui-button variant="ghost" color="neutral" size="2xsmall" [onlyIcon]="true">` | canon (README de `sui-button`: tabla densa = `2xsmall`) |
77
+ | prender/apagar | `<sui-switch>` | propuesta, falta aprobación |
78
+ | calificación | `<sui-rating>` de solo lectura | propuesta, falta aprobación |
79
+
80
+ Si la columna tiene plantilla, `descriptionKey` y `truncate` **no se aplican**: solo funcionan en
81
+ el render de texto plano. Por eso `orders.html:94` deja la columna del pedido sin plantilla.
82
+
83
+ ### Cargar las filas: cuenta + página por cursor
84
+
85
+ **Canon** (`master-pedidos/CLAUDE.md`, «Performance — Firestore reads»): una pantalla con tabla
86
+ **no baja la colección entera** para mostrar 30 filas. Los totales salen de `getCount`, las filas
87
+ de `getCollectionPage` con cursor, y debajo de la tabla va un «Cargar más»
88
+ (`sui-button variant="stroke" color="neutral"`). Referencia: `pages/orders/orders.ts:716`
89
+ (`loadMore`) y `orders.html:132`.
90
+
91
+ - El orden de la tabla lo fija la consulta (`orderBy`). **No uses `sortable`** sobre una lista
92
+ paginada: ordenaría solo lo cargado y mentiría sobre el resto.
93
+ - `sui-pagination` (números de página) no calza con cursor: Firestore no salta a «la página 7».
94
+
95
+ ### Vacío y carga
96
+
97
+ - `loading` pinta 5 filas esqueleto con las columnas reales. Úsalo mientras llega la primera
98
+ página. Canon (memoria del proyecto: skeleton obligatorio en pantalla nueva).
99
+ - `emptyMessage` es una línea con un ícono de bandeja, sin ilustración ni botón. **Canon**
100
+ (CANON-SUI §5.6): no se mezcla con `sui-empty-state`. Escríbelo como en la app, una frase con
101
+ punto: «Nadie recargó en este período.», «Sin gastos registrados todavía.». El default
102
+ `'No hay datos'` no dice nada: siempre se pasa uno propio (los 9 usos lo hacen).
103
+
104
+ ## Tamaño en mobile y en web
105
+
106
+ `size` cambia **el alto mínimo de la fila**, no la letra (14 px en todos):
107
+
108
+ | size | alto | nombre en el Figma | fuente |
109
+ |---|---|---|---|
110
+ | `small` | 48 | Large | Figma |
111
+ | `medium` (default) | 64 | X-Large | Figma |
112
+ | `large` | 80 | — | extensión SUI |
113
+ | `xlarge` | 96 | — | extensión SUI |
114
+
115
+ Uso de hoy: 2 `medium` explícitos (`orders`, `drivers`), 1 `small` (`master-overview`, una tabla
116
+ secundaria dentro de un tablero) y 6 sin `size` (o sea `medium`).
117
+
118
+ | uso | mobile | web | fuente |
119
+ |---|---|---|---|
120
+ | tabla principal de una pantalla | `medium` | `medium` | práctica de la app |
121
+ | tabla secundaria dentro de un tablero | `small` | `small` | práctica de la app (1 uso) |
122
+ | filas con dos líneas y un badge | `medium` | `medium` | propuesta, falta aprobación |
123
+
124
+ El patrón `[size]="bp.isMobile() ? 'large' : 'medium'"` de los botones **no aplica aquí**: la fila
125
+ ya mide 64 y el toque es sobre toda la fila. `large`/`xlarge` no tienen uso hoy.
126
+
127
+ **Qué pasa en mobile (≤ 767 px):** la tabla scrollea en horizontal dentro de sí misma; la
128
+ cabecera y el cuerpo miden lo mismo y la barra de scroll se oculta. En la app conviven dos
129
+ salidas, y las dos son práctica, no regla:
130
+
131
+ | salida | dónde | cuándo elegirla (propuesta) |
132
+ |---|---|---|
133
+ | la misma tabla, con scroll lateral | `orders`, `master-orders`, `central-gastos`, `central-ingresos` | 3-4 columnas, y la primera (`leading`) ya dice casi todo |
134
+ | tarjetas en grilla por debajo de 1350 px | `drivers` (`drivers.ts:282`), `master-gastos` | 6+ columnas: con el sidebar abierto no entran ni en laptop ni en iPad |
135
+
136
+ **Android (canon del componente, no se repite en la página):** un contenedor con
137
+ `overflow-x: auto` se queda con el gesto vertical y la página deja de scrollear sobre la tabla.
138
+ Ningún `touch-action` solo lo arregla (probado en device). `sui-table` lo resuelve **por dentro y
139
+ solo en Android**: pone `touch-action: pan-x` por JS para que el scroll horizontal sea el nativo,
140
+ y mueve él mismo la página con el gesto vertical (con inercia). En iOS no hace nada: WebKit delega
141
+ el vertical solo. Por eso la página **no** agrega `touch-action` ni un wrapper con `overflow-x`.
142
+
143
+ ## «No usar»: por qué no hoy y cuándo sí
144
+
145
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
146
+ |---|---|---|
147
+ | `sortable` | toda tabla de la app es una lista paginada por cursor: el orden lo da la consulta | una tabla chica que se carga entera (≤ 50 filas, un reporte del mes) |
148
+ | `selectable` | no hay acciones en lote | aprobar o rechazar varias recargas de una vez, exportar filas elegidas |
149
+ | `striped` | con divisor entre filas ya se lee la fila; las rayas suman ruido | tablas de muchas columnas numéricas (un reporte contable) |
150
+ | `sticky` | la columna fija tapó otra en 375 px (`central-gastos.ts:80`) | una tabla ancha en desktop donde la primera columna es la única referencia |
151
+ | `large` / `xlarge` | 64 ya alcanza al dedo | filas con avatar grande y tres líneas |
152
+
153
+ ## API
154
+
155
+ <!-- api:inicio -->
156
+ <!-- generada: no editar a mano — `npm run docs` -->
157
+
158
+ ### `sui-table`
159
+
160
+ | input | tipo | default |
161
+ |---|---|---|
162
+ | `columns` | `SuiTableColumn<T>[]` | — (requerido) |
163
+ | `rows` | `T[]` | — (requerido) |
164
+ | `size` | `'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` |
165
+ | `loading` | `boolean` | `false` |
166
+ | `emptyMessage` | `string` | `'No hay datos'` |
167
+ | `striped` | `boolean` | `false` |
168
+ | `selectable` | `boolean` | `false` |
169
+ | `rowKey` | `keyof T \| ((row: T) => any)` | `undefined` |
170
+ | `selectedRows` · model, `[(selectedRows)]` | `Set<any>` | `new Set()` |
171
+ | `sortKey` · model, `[(sortKey)]` | `string \| null` | `null` |
172
+ | `sortDir` · model, `[(sortDir)]` | `'asc' \| 'desc' \| null` | `null` |
173
+
174
+ | output | payload |
175
+ |---|---|
176
+ | `rowClicked` | `T` |
177
+ | `sortChanged` | `{ key: string; dir: 'asc' \| 'desc' \| null }` |
178
+
179
+ ### `[suiTableCell]`
180
+
181
+ | input | tipo | default |
182
+ |---|---|---|
183
+ | `suiTableCell` | `string` | — (requerido) |
184
+
185
+ Outputs: ninguno.
186
+ <!-- api:fin -->
187
+
188
+ ```ts
189
+ interface SuiTableColumn<T = any> {
190
+ key: string; // campo de la fila; único
191
+ label: string; // '' para la columna de acciones
192
+ width?: string; // ancho fijo ('100px', '20%')
193
+ minWidth?: string; // piso (manda en el scroll de mobile)
194
+ maxWidth?: string;
195
+ align?: 'left' | 'center' | 'right';
196
+ sortable?: boolean;
197
+ disabled?: boolean; // cabecera apagada, sin orden
198
+ priority?: 'regular' | 'leading' | 'passive' | 'none';
199
+ descriptionKey?: string; // segunda línea (solo texto plano)
200
+ truncate?: boolean; // «…» + tooltip (solo texto plano)
201
+ misc?: SuiTableCellMisc; // documental: qué componente va en la plantilla
202
+ sticky?: boolean; // fija la columna y activa el scroll lateral en cualquier ancho
203
+ }
204
+ ```
205
+
206
+ `SuiTableCellDirective` se importa aparte (`<ng-template suiTableCell="key" let-row>`).
207
+ El orden cicla `asc → desc → sin orden` y emite `sortChanged` en cada paso; ordenar las filas es
208
+ trabajo de la página.
209
+
210
+ ## Trampas
211
+
212
+ - **Por encima de 767 px la tabla recorta, no scrollea.** Sin una columna `sticky`, el contenedor
213
+ es `overflow: hidden` en desktop: lo que no entra **desaparece** a la derecha (monto, estado,
214
+ acciones). Las columnas tienen que entrar en el ancho real, que con el sidebar abierto es
215
+ ~483 px en iPad y ~1015 px en una pantalla de 1351. Lo comentan tres pantallas:
216
+ `pages/drivers/drivers.html:210`, `pages/super-admin/master-gastos/master-gastos.html:38`,
217
+ `pages/central/central-gastos/central-gastos.ts:76`. El wrapper `drivers__table-wrap` es un
218
+ respaldo de eso; no se copia.
219
+ - `sticky` en **cualquier** columna activa el scroll lateral en todos los anchos (no solo en mobile)
220
+ y quita el recorte redondeado de las filas.
221
+ - `descriptionKey` y `truncate` **se ignoran** si la columna tiene plantilla `suiTableCell`.
222
+ - Sin `rowKey`, la fila se identifica por su índice: la selección se mezcla al reordenar o al cargar
223
+ más. Si usas `selectable` o la lista cambia en vivo, pasa `rowKey="id"`.
224
+ - `rowClicked` dispara en **toda** la fila. Un botón dentro de una plantilla necesita
225
+ `$event.stopPropagation()` o abre el detalle además de hacer lo suyo.
226
+ - `toggleAll` selecciona **lo cargado**, no la colección: con cursor, «todas» son las de la página.
227
+ - El hover de fila solo existe con mouse (`@media (hover: hover)`); en táctil no hay feedback de
228
+ toque: si la fila abre algo, que se note por el contenido (una pista «Toca un gasto para…», como
229
+ `central-gastos.html:46`).
230
+ - La fila esqueleto son siempre 5, sin importar el tamaño de página.
231
+ - No le pongas `touch-action` ni `overflow-x` desde la página: el componente ya maneja el gesto en
232
+ Android y lo romperías.
233
+
234
+ ## Ejemplo
235
+
236
+ ```html
237
+ <sui-card>
238
+ <sui-table
239
+ [columns]="columnas"
240
+ [rows]="filas()"
241
+ rowKey="id"
242
+ [loading]="cargando()"
243
+ emptyMessage="Sin pedidos en este estado."
244
+ (rowClicked)="abrirDetalle($event)">
245
+ <ng-template suiTableCell="estado" let-row>
246
+ <sui-badge [color]="row.color" variant="lighter" size="small">{{ row.estadoLabel }}</sui-badge>
247
+ </ng-template>
248
+ </sui-table>
249
+
250
+ @if (hayMas()) {
251
+ <sui-button variant="stroke" color="neutral" [size]="bp.isMobile() ? 'large' : 'small'"
252
+ [disabled]="cargandoMas()" (click)="cargarMas()">Cargar más</sui-button>
253
+ }
254
+ </sui-card>
255
+ ```
256
+
257
+ ```ts
258
+ readonly columnas: SuiTableColumn[] = [
259
+ { key: 'numero', label: 'Pedido', priority: 'leading', descriptionKey: 'desdeHace', minWidth: '130px' },
260
+ { key: 'cliente', label: 'Cliente', minWidth: '160px', maxWidth: '220px', truncate: true },
261
+ { key: 'total', label: 'Total', align: 'right', width: '100px' },
262
+ { key: 'estado', label: 'Estado', width: '130px' },
263
+ ];
264
+ ```
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: sui-tag-input
3
+ selector: sui-tag-input
4
+ resumen: Campo donde se escriben varios valores cortos que se van sumando como chips (palabras clave)
5
+ alternativas: [sui-input (un solo valor), sui-select (una opción de una lista), sui-checkbox-label (varias opciones fijas)]
6
+ ---
7
+
8
+ # sui-tag-input
9
+
10
+ Story: [Molecules/Input/Tag Input](https://sui-design-system.web.app/?path=/docs/molecules-input-tag-input--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para una lista de valores **libres y cortos** que la persona escribe uno por uno: palabras
15
+ clave, rubros, sinónimos. Enter (y la coma, si se pide) convierte lo escrito en un chip; la ✕ de
16
+ cada chip lo quita; borrar con el campo vacío quita el último.
17
+
18
+ **No lo uses** para:
19
+
20
+ - Elegir varias opciones que ya existen: casillas (`sui-checkbox-label`).
21
+ - Un solo valor: `sui-input`.
22
+ - Una lista que se guarda como texto separado por comas y nadie ve como lista.
23
+
24
+ Regla corta: **varios valores libres → tag-input · varias opciones fijas → casillas.**
25
+
26
+ ## Qué variante usar
27
+
28
+ > **En la guía del DS es un ejemplo de su página del input** («With Tags»: un campo con los chips
29
+ > Berlin, London, Paris debajo). No dice cuándo usarlo.
30
+ >
31
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**. Candidato: «Rubros que entran»
32
+ > en `pages/super-admin/master-settings/master-settings.html:134`, un `sui-input` donde se escriben
33
+ > palabras separadas por coma («ferreteria, ferretería, construcción») y la pantalla las parte a mano.
34
+
35
+ | prop / evento | cuándo | fuente |
36
+ |---|---|---|
37
+ | `label` · `required` · `sublabel` · `tooltip` · `hint` | Igual que `sui-input`. El hint dice cómo se agrega: «Escribe y presiona Enter». | según la guía del DS (ejemplo con label «Tag Input») |
38
+ | `placeholder` | **Siempre**: el default es «Add tags...». «Agrega una palabra…». | propuesta, falta aprobación |
39
+ | `commaSeparator` | `true` cuando la gente ya escribe con comas (rubros, sinónimos). | propuesta, falta aprobación |
40
+ | `addOnBlur` | `true` en mobile: si sale del campo sin Enter, lo escrito no se pierde. | propuesta, falta aprobación |
41
+ | `maxTags` | Cuando hay un tope real. Hoy no hay. | propuesta, falta aprobación |
42
+ | `allowDuplicates` | `false` (default). | propuesta, falta aprobación |
43
+ | `rightIcon` | No usar hoy (ícono de info a la derecha). | — |
44
+ | `error` + `errorMessage` | Falta al menos un valor en un campo obligatorio. | según la guía del DS (estado de error del Figma) |
45
+ | `disabled` | Mientras se guarda. | API |
46
+ | `(tagAdded)` · `(tagRemoved)` | Guardar al momento, sin botón. | API |
47
+
48
+ ## Tamaño en mobile y en web
49
+
50
+ Publicado (0.19.0): `xsmall` (32), `small` (36), `medium` (40). **En el repo del DS, sin publicar:**
51
+ `large` (48) y `xlarge` (52), los mismos altos de `sui-input`, y el texto a 16 px en mobile (en
52
+ 0.19.0 queda en 14). Los chips de abajo son `sui-tag` de tamaño fijo: **la ✕ para quitar un chip
53
+ no crece** con el campo.
54
+
55
+ En large y xlarge el texto sube a 16 px (regla del 03-10-2026): lo que se escribe va en 16/24 también
56
+ en web; el alto sigue en 48/52. Los chips de abajo no cambian.
57
+
58
+ | uso | mobile | web | fuente |
59
+ |---|---|---|---|
60
+ | campo de formulario | `large` (48) **cuando se publique**; hasta entonces `medium` (40) | `medium` (40) | propuesta, falta aprobación |
61
+
62
+ ```html
63
+ <sui-tag-input [size]="bp.isMobile() ? 'large' : 'medium'" … /> <!-- 'large' desde la próxima versión -->
64
+ ```
65
+
66
+ ## «No usar»: por qué no hoy y cuándo sí
67
+
68
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
69
+ |---|---|---|
70
+ | el componente entero (0 usos) | nadie carga listas de palabras | «Rubros que entran» de una categoría del home (`master-settings.html:134`), o sinónimos de un producto para el buscador |
71
+ | `rightIcon` | el hint explica mejor | — |
72
+ | `maxTags` | no hay topes | un máximo de 5 palabras clave por producto |
73
+ | `allowDuplicates` | repetir no sirve | — |
74
+ | `size` `xsmall` / `small` | no hay tag-inputs en tablas | un filtro denso del panel |
75
+
76
+ ## API
77
+
78
+ <!-- api:inicio -->
79
+ <!-- generada: no editar a mano — `npm run docs` -->
80
+
81
+ | input | tipo | default |
82
+ |---|---|---|
83
+ | `size` | `'xsmall' \| 'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` |
84
+ | `placeholder` | `string` | `'Add tags...'` |
85
+ | `disabled` | `boolean` | `false` |
86
+ | `error` | `boolean` | `false` |
87
+ | `errorMessage` | `string \| null` | `null` |
88
+ | `rightIcon` | `string \| boolean` | `false` |
89
+ | `label` | `string \| null` | `null` |
90
+ | `sublabel` | `string \| null` | `null` |
91
+ | `required` | `boolean` | `false` |
92
+ | `tooltip` | `string \| null` | `null` |
93
+ | `hint` | `string \| null` | `null` |
94
+ | `commaSeparator` | `boolean` | `false` |
95
+ | `allowDuplicates` | `boolean` | `false` |
96
+ | `maxTags` | `number \| null` | `null` |
97
+ | `addOnBlur` | `boolean` | `false` |
98
+ | `value` · model, `[(value)]` | `string[]` | `[]` |
99
+
100
+ | output | payload |
101
+ |---|---|
102
+ | `tagAdded` | `string` |
103
+ | `tagRemoved` | `string` |
104
+ <!-- api:fin -->
105
+
106
+ `value` es `model<string[]>`. Implementa `ControlValueAccessor`: lo que está escrito sin confirmar
107
+ no es parte del valor. Slot `[slot=right]` para un contenido propio a la derecha.
108
+
109
+ ## Trampas
110
+
111
+ - **Placeholder en inglés por default** («Add tags...»). Pásalo siempre.
112
+ - Sin `commaSeparator` la coma es un carácter más: «a, b» queda como un solo chip.
113
+ - Sin `addOnBlur`, lo que se escribió y no se confirmó con Enter se pierde al salir.
114
+ - Los chips van **debajo** del campo, no adentro.
115
+ - El hover quita el borde (a diferencia de `sui-input`, que lo oscurece).
116
+
117
+ ## Ejemplo
118
+
119
+ ```html
120
+ <sui-tag-input label="Rubros que entran" placeholder="Agrega una palabra…"
121
+ hint="Un local entra si su rubro contiene alguna" [commaSeparator]="true"
122
+ [addOnBlur]="true" [(value)]="rubros" />
123
+ ```