@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,104 @@
1
+ ---
2
+ name: sui-inline-input
3
+ selector: sui-inline-input
4
+ resumen: Texto que se edita en su lugar: en reposo muestra el dato y un lápiz; al tocarlo, campo con ✓ y ✕
5
+ alternativas: [sui-input (campo de formulario, siempre editable), sui-modal con sui-input (editar con más contexto o varios datos a la vez)]
6
+ ---
7
+
8
+ # sui-inline-input
9
+
10
+ Story: [Molecules/Input/Inline Input](https://sui-design-system.web.app/?path=/docs/molecules-input-inline-input--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para corregir **un solo dato de una línea** en una lista o ficha, sin abrir un modal:
15
+ renombrar una categoría de la carta, el alias de una dirección. En reposo se lee como texto con un
16
+ lápiz; al tocar el lápiz pasa a campo con ✓ (confirmar) y ✕ (cancelar). Enter confirma y Escape
17
+ cancela.
18
+
19
+ **No lo uses** para:
20
+
21
+ - Un formulario: `sui-input`, que siempre está editable.
22
+ - Varios datos que se editan juntos o que necesitan explicación: un modal con sus `sui-input`.
23
+ - Un dato con validación compleja o que dispara algo grande (precio, estado): mejor un flujo con
24
+ confirmación.
25
+
26
+ Regla corta: **un dato, en su lugar, que se corrige rápido → inline · varios o delicados → modal.**
27
+
28
+ ## Qué variante usar
29
+
30
+ > **La guía del DS no tiene página para este componente.** Viene del Figma («Inline Input»).
31
+ > Todo lo de abajo es **propuesta, falta aprobación**.
32
+ >
33
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**. Hay listas con campos siempre
34
+ > abiertos que se parecen (`pages/super-admin/master-settings/master-settings.html:99`, nombre de
35
+ > cada categoría del home), pero ahí se editan varias filas y se guarda todo junto: está bien con
36
+ > `sui-input`.
37
+
38
+ | prop / evento | cuándo | fuente |
39
+ |---|---|---|
40
+ | `value` | El dato. `[(value)]` o `formControlName`, nunca los dos. | API |
41
+ | `placeholder` | Lo que se ve si está vacío: «Sin nombre». **Siempre**: el default es «Placeholder text...». | propuesta, falta aprobación |
42
+ | `leftIcon` | Un ícono que dice qué dato es (`price-tag-3-line` para una categoría). `false` si el texto se explica solo. El default es `user-3-line`. | propuesta, falta aprobación |
43
+ | `editLabel` | Nombre accesible del lápiz: «Editar nombre». | propuesta, falta aprobación |
44
+ | `error` | El valor escrito no sirve (vacío, repetido). Solo se ve en edición; no trae mensaje. | propuesta, falta aprobación |
45
+ | `disabled` | Mientras se guarda. | propuesta, falta aprobación |
46
+ | `(confirm)` | Guardar. Llega el texto nuevo. | API |
47
+ | `(cancel)` · `(edit)` | Volver atrás sin guardar · avisar que se empezó a editar. | API |
48
+
49
+ ## Tamaño en mobile y en web
50
+
51
+ **No tiene `size`**: mide 36 px de alto y sus botones (lápiz, ✓, ✕) son de 24 × 24. El texto ya
52
+ sube a 16 px en mobile (sin zoom de iOS). **No tiene tamaño suficiente para mobile**: los botones
53
+ de 24 px quedan lejos de los 44 que pide un dedo. Usarlo en el panel web; en el teléfono, editar
54
+ con un `sui-input` `large`.
55
+
56
+ | uso | mobile | web |
57
+ |---|---|---|
58
+ | renombrar en una lista | `sui-input` `large` en un modal | este, 36 px |
59
+
60
+ ## «No usar»: por qué no hoy y cuándo sí
61
+
62
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
63
+ |---|---|---|
64
+ | el componente entero (0 usos) | no hay datos que se corrijan sueltos en una lista | renombrar una categoría de la carta en web, o el alias de una dirección guardada |
65
+ | `leftIcon` default (`user-3-line`) | casi nunca el dato es una persona | el nombre de un miembro del equipo |
66
+
67
+ ## API
68
+
69
+ <!-- api:inicio -->
70
+ <!-- generada: no editar a mano — `npm run docs` -->
71
+
72
+ | input | tipo | default |
73
+ |---|---|---|
74
+ | `value` · model, `[(value)]` | `string` | `''` |
75
+ | `placeholder` | `string` | `'Placeholder text...'` |
76
+ | `leftIcon` | `string \| false` | `'user-3-line'` |
77
+ | `error` | `boolean` | `false` |
78
+ | `disabled` | `boolean` | `false` |
79
+ | `editLabel` | `string` | `'Editar'` |
80
+
81
+ | output | payload |
82
+ |---|---|
83
+ | `confirm` | `string` |
84
+ | `cancel` | `void` |
85
+ | `edit` | `void` |
86
+ <!-- api:fin -->
87
+
88
+ Implementa `ControlValueAccessor`.
89
+
90
+ ## Trampas
91
+
92
+ - **Los defaults están pensados para la story**: `placeholder` «Placeholder text...» y
93
+ `leftIcon` `user-3-line`. Pasa los dos siempre.
94
+ - `error` no trae mensaje: decir qué falta en otro lado (toast o hint cercano).
95
+ - Cancelar restaura el texto de antes; `(confirm)` es el único momento de guardar.
96
+ - Ocupa todo el ancho de su contenedor.
97
+
98
+ ## Ejemplo
99
+
100
+ ```html
101
+ <sui-inline-input [(value)]="nombreCategoria" placeholder="Sin nombre" leftIcon="price-tag-3-line"
102
+ editLabel="Editar nombre" [error]="!nombreCategoria().trim()"
103
+ (confirm)="renombrar($event)" />
104
+ ```
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: sui-inline-select
3
+ selector: sui-inline-select
4
+ resumen: Selector sin caja que vive dentro de una frase o al final de un campo («Mostrar en USD ▾»)
5
+ alternativas: [sui-compact-select (filtro suelto con caja propia), sui-compact-select-for-input (al inicio de un sui-input, con divisor), sui-select (campo de formulario)]
6
+ ---
7
+
8
+ # sui-inline-select
9
+
10
+ Story: [Molecules/Select/Inline Select](https://sui-design-system.web.app/?path=/docs/molecules-select-inline-select--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para cambiar un valor sin romper la lectura: dentro de una frase («Tus precios se muestran
15
+ en **USD ▾**») o al final de un `sui-input` (una unidad o moneda a la derecha). No tiene borde, ni
16
+ fondo, ni padding: se lee como texto con una flecha.
17
+
18
+ **No lo uses** para:
19
+
20
+ - Un campo de formulario: `sui-select`.
21
+ - Un filtro suelto en una barra: `sui-compact-select`.
22
+ - Algo que la persona tiene que encontrar sin buscarlo: sin caja no parece un control. Si el dato
23
+ importa, va en un campo.
24
+
25
+ Regla corta: **dentro de una frase o al final de un campo → inline · campo → select.**
26
+
27
+ ## Qué variante usar
28
+
29
+ > **En la guía del DS es la variante `inline` del select**, con dos ejemplos: país y «con input»
30
+ > (al final de un campo). La página del input lo repite en «With Inline Select». No dice cuándo.
31
+ >
32
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**.
33
+
34
+ | prop | cuándo | fuente |
35
+ |---|---|---|
36
+ | `type="icon"` (default) | Con un ícono delante (atenuado hasta que se elige). | según la guía del DS |
37
+ | `type="country"` + `flag` | No usar hoy: todo es Perú. | según la guía del DS (ejemplo «Country») |
38
+ | `items` | `{ value, label, sublabel?, icon?, flag?, disabled? }`. | API |
39
+ | `selected` / `selectedChange` | La opción elegida. O `formControlName`, nunca los dos. | API |
40
+ | `value` · `icon` · `flag` | Lo que se ve si no hay `selected`. | API |
41
+ | `disabled` | Mientras carga. | API |
42
+ | `clicked` | Solo si la pantalla arma su propio menú. | API |
43
+
44
+ ## Tamaño en mobile y en web
45
+
46
+ **No tiene `size`**: mide 20 px de alto (la línea del texto). Dentro de una frase el área que se toca
47
+ es la palabra, muy por debajo de los 44 px. **No tiene tamaño suficiente para mobile**: en el
48
+ teléfono, solo dentro de un `sui-input` (que da el alto) o con la frase en una fila que se toque
49
+ entera.
50
+
51
+ | uso | mobile | web |
52
+ |---|---|---|
53
+ | dentro de una frase | evitar: usar `sui-select` | 20 px, en línea con el texto |
54
+ | al final de un `sui-input` | el alto lo da el input (`large`) | el alto lo da el input (`medium`) |
55
+
56
+ ## «No usar»: por qué no hoy y cuándo sí
57
+
58
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
59
+ |---|---|---|
60
+ | el componente entero (0 usos) | ninguna frase de la app cambia un valor | «Mostrar ventas de **esta semana ▾**» en el inicio del local (web) |
61
+ | `type="country"` | todo es Perú | si se abre otro país |
62
+
63
+ ## API
64
+
65
+ <!-- api:inicio -->
66
+ <!-- generada: no editar a mano — `npm run docs` -->
67
+
68
+ | input | tipo | default |
69
+ |---|---|---|
70
+ | `type` | `'icon' \| 'country'` | `'icon'` |
71
+ | `value` | `string` | `''` |
72
+ | `icon` | `string \| null` | `null` |
73
+ | `flag` | `SuiFlagName \| null` | `null` |
74
+ | `items` | `readonly SuiInlineSelectItem<T>[]` | `[]` |
75
+ | `selected` · model, `[(selected)]` | `SuiInlineSelectItem<T> \| null` | `null` |
76
+ | `disabled` | `boolean` | `false` |
77
+
78
+ | output | payload |
79
+ |---|---|
80
+ | `clicked` | `Event` |
81
+ | `selectedChange` | `SuiInlineSelectItem<T>` |
82
+ <!-- api:fin -->
83
+
84
+ Implementa `ControlValueAccessor`.
85
+
86
+ ## Trampas
87
+
88
+ - **Riesgo en iPhone (no verificado en dispositivo):** el host escucha `touchend` y hace `blur()`,
89
+ el mismo patrón que en `sui-compact-select` cancelaba el clic de iOS. Probar en un iPhone antes
90
+ del primer uso.
91
+ - Al final de un `sui-input` va en `data-slot="end"` con `[trailingControl]="true"`.
92
+ - No mezclar `[(selected)]` con `formControlName`.
93
+ - El menú mide al menos 200 px aunque el texto sea corto.
94
+
95
+ ## Ejemplo
96
+
97
+ ```html
98
+ <p>Mostrar ventas de
99
+ <sui-inline-select [items]="periodos" [(selected)]="periodo" />
100
+ </p>
101
+ ```
@@ -0,0 +1,244 @@
1
+ ---
2
+ name: sui-input
3
+ selector: sui-input
4
+ resumen: Campo de una sola línea para escribir un dato (nombre, teléfono, monto, búsqueda)
5
+ alternativas: [sui-textarea (texto de varias líneas), sui-select (elegir de una lista), sui-counter-input (una cantidad con − y +), sui-tag-input (varios valores cortos), sui-otp-input (código de verificación), sui-inline-input (editar un dato en su lugar, con lápiz)]
6
+ ---
7
+
8
+ # sui-input
9
+
10
+ Story: [Molecules/Input/Input](https://sui-design-system.web.app/?path=/docs/molecules-input-input--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para todo dato de una línea que la persona escribe: nombre, correo, teléfono, DNI,
15
+ placa, dirección, referencia, monto, fecha, y para los buscadores. Trae adentro el label, el
16
+ hint, el error, el prefijo `S/`, la X de limpiar y el ojo de la contraseña: no se arman a mano.
17
+
18
+ **No lo uses** para:
19
+
20
+ - Texto que puede ocupar más de una línea (motivo, nota al repartidor, descripción): `sui-textarea`.
21
+ - Elegir entre opciones que ya existen: `sui-select` (o radio / segmented si son pocas).
22
+ - Una cantidad que se sube y se baja de a uno: `sui-counter-input`.
23
+ - Un código de verificación de N dígitos: `sui-otp-input`.
24
+ - Varios valores cortos que se van sumando: `sui-tag-input`.
25
+ - Un `<input>` nativo con CSS propio: hoy quedan **6** (`pages/products/add-product/add-product.html:130`,
26
+ `:143`, `:189`, `:275`, `pages/apps/apps.html:12`, `pages/employees/employees.html:43`).
27
+
28
+ Regla corta: **una línea → input · varias → textarea · de una lista → select · cantidad → counter.**
29
+
30
+ ## Qué variante usar
31
+
32
+ > **La guía del DS casi no dice «cuándo».** Su página del input lo define como el campo para
33
+ > escribir un valor de una sola línea y muestra ejemplos (ícono, tamaños, afijos, label y hint,
34
+ > contraseña con ojo, contraseña con niveles, error, con botón, tarjeta, con select, con tags,
35
+ > fecha y contador) sin do/don't. Lo marcado «según la guía del DS» sale de esos ejemplos; el
36
+ > resto es **propuesta, falta aprobación**, salvo lo que ya es canon (CLAUDE.md, CANON-SUI).
37
+ >
38
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-badge`, más
39
+ > `master-ejercicios`): **194 usos en 61 archivos**. Tipos: 53 `text`, 36 sin `type` (`text`),
40
+ > 32 `amount`, 22 `number`, 18 `tel`, 10 `email`, 10 `search`, 7 `date`, 3 `password`, 2 `url`,
41
+ > 1 `datetime-local`. Tamaños: 163 sin `size` (`medium`), 18 `large`, 3 `xlarge`, 3 `medium`,
42
+ > 2 `small`, 5 dinámicos.
43
+
44
+ ### `type` — qué dato es
45
+
46
+ El `type` decide el teclado del teléfono y el filtro de teclas. Por dentro, `number`, `tel`,
47
+ `amount` y `card` son un `<input type="text">`: el componente filtra lo que se tipea.
48
+
49
+ | type | cuándo | fuente |
50
+ |---|---|---|
51
+ | `text` (default) | Nombre, dirección, referencia, placa, DNI (con `inputmode="numeric"`). | según la guía del DS (tipo nativo) |
52
+ | `email` | Correo. Pide el teclado con `@`. Va con `autocomplete="email"`. | según la guía del DS (ejemplo con label y hint) |
53
+ | `tel` | Celular peruano de 9 dígitos, con `[maxlength]="9"` y placeholder `987 654 321`. | según la guía del DS (tipo nativo); el formato es propuesta |
54
+ | `amount` | **Todo monto en soles**, siempre con `prefix="S/"` y placeholder `0.00`. Pone comas de miles y dos decimales al salir. | canon (CANON-SUI §5.5, 27-07-2026) |
55
+ | `number` | Un entero que no es dinero: minutos, kilómetros, cupos, porcentaje. Si es dinero, `amount`. | propuesta, falta aprobación |
56
+ | `search` | **Todo buscador.** Pide la lupa en el teclado sola (y no lleva `inputmode`). | canon (CLAUDE.md «La lupa del teclado», 14-08-2026) |
57
+ | `password-toggle` | **Toda contraseña**: login y registro. El ojo deja revisar lo escrito en el teléfono. | según la guía del DS (sus dos ejemplos de contraseña traen el ojo) |
58
+ | `password` | No usar: sin ojo, en el teléfono no se puede revisar lo tipeado. | propuesta, falta aprobación |
59
+ | `date` / `datetime-local` | Una fecha suelta en un formulario del panel («Válido hasta», «Fecha del gasto»). Abre el selector nativo. Para un calendario propio, ver `sui-datepicker`. | propuesta, falta aprobación |
60
+ | `url` | Un enlace (imagen de campaña). | según la guía del DS (ejemplo con afijo `https://`) |
61
+ | `card` | No usar hoy: no hay pago con tarjeta. | según la guía del DS (ejemplo «Payment Input») |
62
+ | `button` | No usar hoy: campo de solo lectura con una acción al lado (copiar enlace). | según la guía del DS (ejemplo «With Button») |
63
+
64
+ **Usos de hoy que rompen la regla:**
65
+
66
+ - **11 buscadores con `type="text"`** → `type="search"` (hoy no sale la lupa en el teclado):
67
+ `pages/customers/customers.html:28`, `pages/products/products.html:71`,
68
+ `components/map-picker-modal/map-picker-modal.html:14`, `pages/settings/team/team.html:59`,
69
+ `pages/super-admin/super-admin.html:63`…
70
+ - **21 `amount` sin `prefix="S/"`**, 15 de ellos con «(S/)» metido en el label:
71
+ `pages/super-admin/master-settings/master-settings.html:273` («Mínimo (S/)»),
72
+ `pages/business-onboarding/step-product/step-product.html:33` («Precio (S/)», placeholder «Ej: 25»),
73
+ `pages/super-admin/master-drivers/master-drivers.html:99`.
74
+ - **2 montos con `type="number"`** → `amount` + `prefix`: `pages/super-admin/super-admin.html:649`
75
+ («Monto a cobrar»), `pages/super-admin/master-gastos/master-gastos.html:193` («Monto»).
76
+ - **3 `password` sin ojo** → `password-toggle`: `pages/auth/login/login.html:71`,
77
+ `pages/auth/register/register.html:121`, `:133`.
78
+
79
+ ### Las piezas
80
+
81
+ | prop | cuándo | fuente |
82
+ |---|---|---|
83
+ | `label` | Siempre, salvo en un buscador (ahí el placeholder y la lupa ya dicen qué es). Una a tres palabras, sentence case. | según la guía del DS (ejemplo con label); largo: canon (CANON-SUI §5.4) |
84
+ | `required` | Campo obligatorio: asterisco rojo. | según la guía del DS (`Email Address*`) |
85
+ | `sublabel` | **«(Opcional)»**, entre paréntesis, en un campo que no es obligatorio, en vez de escribir «(opcional)» en el label. También «(No editable)». El componente no pone los paréntesis: sin ellos queda pegado («ReferenciaOpcional»). | según la guía del DS (`(Optional)`) y canon (README de `sui-form-label`) |
86
+ | `tooltip` | Una aclaración que no entra en el hint. Hoy sin uso. | según la guía del DS (ícono de info del label) |
87
+ | `hint` | Una línea que ayuda a llenar bien: «9 dígitos, empezando con 9», «Mínimo 6 caracteres». | según la guía del DS |
88
+ | `hintIcon` | No usar hoy: el hint se lee solo; el ícono suma ruido en formularios largos. | propuesta, falta aprobación |
89
+ | `error` + `errorMessage` | El error va **con** su mensaje, que reemplaza al hint. `error` solo pinta el borde. | según la guía del DS (`Has Error`) |
90
+ | `placeholder` | Un ejemplo del formato, no el nombre del campo: «987 654 321», «Ej: Av. Perú 224», «0.00». Elipsis `…`, no `...`. | canon (CANON-SUI §5.2 y §5.5) |
91
+ | `prefix` | `S/` en los montos. Nada más hoy. | canon (CANON-SUI §5.5) |
92
+ | `icon` + `iconPosition` | `search-line` a la izquierda en buscadores; `map-pin-2-line` en un campo de dirección de solo lectura. `right` no tiene caso hoy. | según la guía del DS (ejemplo con ícono); el «cuándo» es propuesta |
93
+ | `showClear` | Buscadores: la X borra la búsqueda de un toque. | propuesta, falta aprobación (ya lo hacen los 3 buscadores del cliente) |
94
+ | `fullRadius` | Solo el buscador principal del cliente (home, carta del local, rubro), que va encima de chips redondos. Los formularios, con el radio normal. | propuesta, falta aprobación (ya es práctica: `pages/customer/store-browser/store-browser.html:43`) |
95
+ | `maxlength` | Teléfono (9), nombres con tope, códigos. | propuesta, falta aprobación |
96
+ | `inputmode` | Solo cuando el `type` no pide el teclado correcto: un DNI o un número de cuenta en `type="text"` → `numeric`. **Nunca `search`.** | canon (CLAUDE.md «La lupa del teclado», 14-08-2026) |
97
+ | `enterkeyhint` | `next` en formularios de varios pasos, `send` en un chat, `done` en el último campo. El buscador ya pide `search` solo. | propuesta, falta aprobación |
98
+ | `autocomplete` | Siempre en datos de la persona: `name`, `given-name`, `email`, `tel`, `current-password`, `new-password`. `off` en buscadores. | propuesta, falta aprobación |
99
+ | `readonly` | Un dato que se muestra en forma de campo pero se elige en otro lado (el distrito que sale del mapa). | propuesta, falta aprobación |
100
+ | `disabled` | Mientras se envía, o cuando el campo no aplica todavía. | según la guía del DS |
101
+ | `min` / `max` | **No hacen nada** (el `<input>` real es `text`). Validar en el componente que lo usa. | código del DS |
102
+ | `paste` | `false` solo si pegar un monto armaría un valor equivocado. Hoy sin uso. | propuesta, falta aprobación |
103
+ | `closeOnOutsideClick` | Déjalo en `true`: tocar fuera baja el teclado. `false` solo si la pantalla maneja el foco a mano (1 uso). | código del DS |
104
+ | `leadingControl` / `trailingControl` / `leadingDivider` / `trailingDivider` | Componer un control adentro: el país del teléfono (`sui-compact-select-for-input` en `[data-slot=start]`) o una acción al lado. Hoy sin uso. | según la guía del DS (ejemplos «With Select» y «With Button») |
105
+
106
+ **Usos de hoy que rompen la regla:**
107
+
108
+ - **Los 24 `sublabel` de hoy van sin paréntesis** (input, textarea y select: 17 «Opcional», 4 «No editable», 2 «Máximo a descontar», 1 «Solo interno») y se leen pegados al label → «(Opcional)»:
109
+ `components/address-form-modal/address-form-modal.html:134`, `pages/discounts/discounts.html:170`,
110
+ `pages/customer/profile/profile.html:234`.
111
+ - **16 «(opcional)» escritos en el label** (input, textarea y select juntos) → `sublabel="(Opcional)"`:
112
+ `pages/driver/driver-profile/driver-profile.html:431` («Placa (opcional)»),
113
+ `pages/super-admin/master-plans/master-plans.html:131`, `pages/super-admin/super-admin.html:661`.
114
+ - **13 placeholders con `...`** (input, textarea y select) → `…`: `pages/customers/customers.html:28`
115
+ («Buscar clientes...»), `pages/products/products.html:71`, `components/address-form-modal/address-form-modal.html:134`.
116
+ - **2 campos sin label en el login** (el placeholder hace de rótulo): `pages/auth/login/login.html:64`, `:71`.
117
+ - **6 campos con el label afuera** (`<sui-form-label>` suelto encima) en el onboarding del
118
+ repartidor: `pages/driver-onboarding/step-info/step-info.html:37`. Funciona, pero el error y el
119
+ `required` quedan desconectados del campo: va el `label` del propio input.
120
+
121
+ ## Tamaño en mobile y en web
122
+
123
+ Los altos de `sui-input` son **los del botón a propósito** (`large` 48 y `xlarge` 52 son
124
+ extensión de SUI): un campo y un botón puestos lado a lado miden lo mismo. La guía del DS solo
125
+ tiene `medium` (40), `small` (36) y `xsmall` (32).
126
+
127
+ | uso | mobile | web | fuente |
128
+ |---|---|---|---|
129
+ | campo de formulario | `large` (48), igual que el botón del footer | `medium` (40) | propuesta, falta aprobación |
130
+ | buscador principal del cliente (home, carta, rubro) | `xlarge` (52) + `fullRadius` | `xlarge` + `fullRadius` | propuesta (ya es práctica en las 3 pantallas) |
131
+ | buscador o filtro de una lista del panel | `large` (48) | `small` (36) | propuesta (ya es práctica en `pages/orders/orders.html:72`) |
132
+ | campo dentro de una tabla o fila densa | no aplica | `xsmall` (32) | según la guía del DS |
133
+
134
+ ```html
135
+ <sui-input [size]="bp.isMobile() ? 'large' : 'medium'" label="Teléfono" type="tel" … />
136
+ <!-- bp = BreakpointsService de la app -->
137
+ ```
138
+
139
+ No uses `bp.controlSize()` en campos: da `xlarge` (52) en mobile, más alto que el botón `large`
140
+ del footer (3 usos hoy, `pages/customer/checkout/checkout.html:392`). El texto sube solo a 16 px
141
+ en mobile (evita el zoom de iOS): no hace falta pedirlo.
142
+
143
+ Hoy: **163 sin `size`** quedan en 40 px también en el teléfono (cumple el mínimo, no alinea con el
144
+ botón `large`).
145
+
146
+ ## «No usar»: por qué no hoy y cuándo sí
147
+
148
+ Nada se borra del DS. Lo marcado «no usar» no tiene caso en la app de hoy; si un flujo nuevo cae
149
+ en la columna de la derecha, se usa.
150
+
151
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
152
+ |---|---|---|
153
+ | `type="card"` | no hay pago con tarjeta | el día que entre Culqi o Niubiz y el número se tipee en la app |
154
+ | `type="button"` | no hay campos con acción pegada | «Copia tu enlace de invitación» o el link público de la tienda con botón de copiar |
155
+ | `type="password"` | sin ojo no se puede revisar en el teléfono | nunca en login; quizá un PIN que no debe mostrarse |
156
+ | `type="time"` | los horarios usan `sui-time-picker` | una hora suelta en un formulario del panel |
157
+ | `hintIcon` | el hint se lee solo | un hint que es advertencia («No se puede cambiar después») |
158
+ | `tooltip` | el hint alcanza | un término técnico del panel (CCI, RUC) que no entra en una línea |
159
+ | `iconPosition="right"` | el ícono de la derecha se confunde con una acción | un dato con unidad visual (un ícono de moto en «Placa») |
160
+ | `paste=false` | pegar ayuda más de lo que rompe | un monto que llega con formato raro desde otra app |
161
+ | afijos con control (`leadingControl`…) | solo hay teléfonos de Perú (+51) | teléfono con código de país, o monto con moneda (ver `sui-compact-select-for-input`) |
162
+ | `size="xsmall"` | no hay campos dentro de tablas | edición rápida de un precio dentro de una tabla del panel |
163
+
164
+ ## API
165
+
166
+ <!-- api:inicio -->
167
+ <!-- generada: no editar a mano — `npm run docs` -->
168
+
169
+ | input | tipo | default |
170
+ |---|---|---|
171
+ | `size` | `'xsmall' \| 'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` |
172
+ | `type` | `'text' \| 'email' \| 'password' \| 'number' \| 'tel' \| 'url' \| 'search' \| 'amount' \| 'card' \| 'password-toggle' \| 'date' \| 'datetime-local' \| 'time' \| 'button'` | `'text'` |
173
+ | `placeholder` | `string` | `''` |
174
+ | `disabled` | `boolean` | `false` |
175
+ | `readonly` | `boolean` | `false` |
176
+ | `fullRadius` | `boolean` | `false` |
177
+ | `error` | `boolean` | `false` |
178
+ | `errorMessage` | `string \| null` | `null` |
179
+ | `label` | `string \| null` | `null` |
180
+ | `hint` | `string \| null` | `null` |
181
+ | `hintIcon` | `string \| null` | `null` |
182
+ | `autocomplete` | `string \| null` | `null` |
183
+ | `inputmode` | `'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url' \| null` | `null` |
184
+ | `enterkeyhint` | `'enter' \| 'done' \| 'go' \| 'next' \| 'previous' \| 'search' \| 'send' \| null` | `null` |
185
+ | `sublabel` | `string \| null` | `null` |
186
+ | `required` | `boolean` | `false` |
187
+ | `tooltip` | `string \| null` | `null` |
188
+ | `value` · model, `[(value)]` | `string` | `''` |
189
+ | `maxlength` | `number \| null` | `null` |
190
+ | `min` | `number \| null` | `null` |
191
+ | `max` | `number \| null` | `null` |
192
+ | `paste` | `boolean` | `true` |
193
+ | `prefix` | `string \| null` | `null` |
194
+ | `showClear` | `boolean` | `false` |
195
+ | `icon` | `string \| null` | `null` |
196
+ | `iconPosition` | `'left' \| 'right'` | `'left'` |
197
+ | `closeOnOutsideClick` | `boolean` | `true` |
198
+ | `leadingDivider` | `boolean` | `false` |
199
+ | `trailingDivider` | `boolean` | `false` |
200
+ | `leadingControl` | `boolean` | `false` |
201
+ | `trailingControl` | `boolean` | `false` |
202
+
203
+ Outputs: ninguno.
204
+ <!-- api:fin -->
205
+
206
+ `value` es `model<string>`: `[(value)]` o `[value]` + `(valueChange)`. También funciona con
207
+ `formControlName` y `[(ngModel)]` (implementa `ControlValueAccessor`).
208
+
209
+ Slots: `[data-slot=start]` y `[data-slot=end]` (íconos que no son RemixIcon o controles
210
+ proyectados con `leadingControl` / `trailingControl`).
211
+
212
+ ## Trampas
213
+
214
+ - **`maxlength` va en minúscula** aquí; en `sui-textarea` es `maxLength`.
215
+ - **`min` y `max` no validan nada**: el `<input>` real es `type="text"` y el navegador los ignora.
216
+ - **`amount` devuelve el número sin comas** («1234.50») aunque muestre «1,234.50», y rellena los
217
+ decimales al salir del campo. Si el padre hace `toFixed` y lo devuelve, compara con `parseFloat`,
218
+ no como texto (ver CLAUDE.md, bug de minOrder). Lo cubren 26 tests en `sui-input.spec.ts`.
219
+ - **Nunca `inputmode="search"`**: en Android tapa la lupa del teclado. `type="search"` ya la pide.
220
+ - **Un `tel` con `[maxlength]="9"` y placeholder `987 654 321`**: el filtro deja escribir espacios;
221
+ quien copie el formato del placeholder llega al tope antes del último dígito. Limpiar espacios en
222
+ el `(valueChange)` o no poner espacios en el placeholder.
223
+ - `showClear` solo aparece con texto escrito y si el campo no está `disabled` ni `readonly`.
224
+ - `error` sin `errorMessage` pinta el borde y no dice qué pasa.
225
+ - El label no crece con el campo: en `large` y `xlarge` se queda en `medium` a propósito.
226
+ - Tocar fuera baja el teclado (`closeOnOutsideClick`). Ya distingue tocar otro campo: no hace falta
227
+ agregar lógica de blur en la pantalla.
228
+ - El `(keydown.enter)` puesto en el host funciona: el evento sube desde el `<input>` de adentro.
229
+
230
+ ## Ejemplo
231
+
232
+ ```html
233
+ <!-- Campo de formulario -->
234
+ <sui-input label="Teléfono" [required]="true" type="tel" [maxlength]="9" autocomplete="tel"
235
+ placeholder="987654321" hint="9 dígitos, empezando con 9"
236
+ [size]="bp.isMobile() ? 'large' : 'medium'" [(value)]="telefono" />
237
+
238
+ <!-- Monto -->
239
+ <sui-input label="Monto de la compra" type="amount" prefix="S/" placeholder="0.00" [(value)]="monto" />
240
+
241
+ <!-- Buscador principal del cliente -->
242
+ <sui-input type="search" icon="search-line" placeholder="Busca negocios…" size="xlarge"
243
+ [fullRadius]="true" [showClear]="true" autocomplete="off" [(value)]="busqueda" />
244
+ ```
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: sui-integration-switch
3
+ selector: sui-integration-switch
4
+ resumen: Integración con otro servicio — logo, nombre, qué hace, botón «Administrar» y el interruptor para prenderla
5
+ alternativas: [sui-switch-card (un ajuste propio de la app, sin logo ni «Administrar»), sui-switch-label (un ajuste en una lista), sui-content-card type="brand" (mostrar que un servicio está conectado, sin prender ni apagar)]
6
+ ---
7
+
8
+ # sui-integration-switch
9
+
10
+ Story: [Molecules/Integration Switch](https://sui-design-system.web.app/?path=/docs/molecules-integration-switch--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para conectar o desconectar un servicio de terceros que además tiene su propia
15
+ configuración: cobrar con tarjeta por Culqi o Izipay, avisar por WhatsApp Business, calcular el
16
+ envío con Mapbox.
17
+
18
+ **No lo uses** para:
19
+
20
+ - Un ajuste de la app (notificaciones, ubicación, disponible): `sui-switch-card` o
21
+ `sui-switch-label`.
22
+ - Mostrar que algo está conectado sin dejar apagarlo: `sui-content-card type="brand"` con un badge.
23
+ - Elegir uno de varios servicios: `sui-radio-card type="brand"`.
24
+
25
+ Regla corta: **servicio externo que se prende y se configura → integration-switch · ajuste propio
26
+ → switch-card.**
27
+
28
+ ## Qué variante usar
29
+
30
+ > **La guía del DS no tiene página de la integration switch.** De la guía sale lo del switch que
31
+ > lleva adentro: **«para prender o apagar un ajuste de forma inmediata»**.
32
+ >
33
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-card`): **0
34
+ > usos**. La app no conecta servicios de terceros desde la interfaz: todo lo de abajo es para
35
+ > cuando lo haga.
36
+
37
+ ### `type` × `appearance`
38
+
39
+ | combinación | cuándo | fuente |
40
+ |---|---|---|
41
+ | `horizontal` · `card` (default) | Una integración suelta en una pantalla de ajustes de web. | propuesta |
42
+ | `vertical` · `card` | Una grilla de integraciones, o la misma en mobile (el «Administrar» baja a ancho completo). | propuesta |
43
+ | `horizontal` · `list` | Una lista de integraciones dentro de una card que ya pone el borde. | propuesta |
44
+ | `vertical` · `list` | Ver «No usar». | propuesta |
45
+
46
+ ### Las piezas
47
+
48
+ | prop | cuándo | fuente |
49
+ |---|---|---|
50
+ | `title` | Siempre. El nombre del servicio: «WhatsApp Business». | propuesta |
51
+ | `description` | Qué hace conectarlo, en una línea: «Avisa al cliente por WhatsApp cuando su pedido sale del local.» | propuesta |
52
+ | `brandIconSrc` | El logo del servicio (`assets/brand/brand-logos/<marca>-original.svg`). | propuesta |
53
+ | `brandIcon` | Un ícono Remix cuando no hay logo. | propuesta |
54
+ | logo proyectado `[sui-integration-brand]` | Un `sui-brand-logo` u otro elemento propio. | propuesta |
55
+ | `manage` (`true`, dice «Administrar») | Siempre que el servicio tenga configuración. El default ya está en español (03-10-2026). | propuesta |
56
+ | `[manage]="false"` | El servicio no se configura: solo se prende. | propuesta |
57
+ | `showToggle=false` | Se administra pero no se apaga desde aquí. | propuesta |
58
+ | `badge` | Integración nueva. Con string sale naranja `light`; pasa el objeto (`purple` para lo nuevo, según el badge). | propuesta |
59
+ | `disabled` | Falta un dato para conectarla (el RUC, la cuenta). | propuesta |
60
+ | `[active]` + `(activeChange)` | El estado. | propuesta |
61
+
62
+ **Usos de hoy que rompen las reglas:** 0 (no hay usos).
63
+
64
+ ## Tamaño en mobile y en web
65
+
66
+ No tiene `size`.
67
+
68
+ | uso | mobile | web |
69
+ |---|---|---|
70
+ | interruptor | **32 × 20 fijo: bajo el piso de mobile** | 32 × 20 |
71
+ | «Administrar» | **36 de alto: bajo el piso de 44** | 36 |
72
+ | layout | `vertical` | `horizontal` |
73
+
74
+ ```html
75
+ <sui-integration-switch [type]="bp.isMobile() ? 'vertical' : 'horizontal'" … />
76
+ ```
77
+
78
+ **No llega al tamaño de mobile.** El switch es `sui-switch` `medium` fijo y el botón mide 36. Antes
79
+ de usarlo en el teléfono hace falta un `size` en el DS (no se inventó aquí).
80
+
81
+ ## «No usar»: por qué no hoy y cuándo sí
82
+
83
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
84
+ |---|---|---|
85
+ | todo el componente (0 usos) | no hay integraciones; además le falta tamaño para mobile | conectar Culqi / Izipay o WhatsApp Business desde el panel del local (web primero) |
86
+ | `vertical` · `list` | sin borde y en columna, el bloque se pierde | casi nunca: una sola integración destacada en una pantalla vacía |
87
+
88
+ ## API
89
+
90
+ <!-- api:inicio -->
91
+ <!-- generada: no editar a mano — `npm run docs` -->
92
+
93
+ | input | tipo | default |
94
+ |---|---|---|
95
+ | `type` | `'horizontal' \| 'vertical'` | `'horizontal'` |
96
+ | `appearance` | `'card' \| 'list'` | `'card'` |
97
+ | `title` | `string` | `''` |
98
+ | `description` | `string \| null` | `null` |
99
+ | `brandIconSrc` | `string \| null` | `null` |
100
+ | `brandIcon` | `string \| null` | `null` |
101
+ | `badge` | `SuiBadgeConfig \| string \| null` | `null` |
102
+ | `manage` | `SuiIntegrationManageConfig \| string \| boolean \| null` | `true` |
103
+ | `showToggle` | `boolean` | `true` |
104
+ | `disabled` | `boolean` | `false` |
105
+ | `active` | `boolean` | `false` |
106
+
107
+ | output | payload |
108
+ |---|---|
109
+ | `activeChange` | `boolean` |
110
+ | `manageClick` | `MouseEvent` |
111
+ <!-- api:fin -->
112
+
113
+ ```ts
114
+ interface SuiIntegrationManageConfig { label?: string; icon?: string; } // icon default 'settings-2-line'
115
+ ```
116
+
117
+ Slot: `[sui-integration-brand]` (reemplaza al logo). Implementa `ControlValueAccessor`.
118
+
119
+ ## Trampas
120
+
121
+ - **`manage` arranca en `true` y dice «Administrar»** (hasta el 03-10-2026 decía «Manage»). Sin
122
+ configuración, `[manage]="false"`.
123
+ - **Es controlada**: `active` es input y `activeChange` solo avisa; sin actualizar tu señal el
124
+ interruptor no se mueve.
125
+ - El estado se llama `active`, no `checked` (como en `sui-switch-label`).
126
+ - El interruptor no tiene `size`: siempre `medium`.
127
+ - En `vertical` el interruptor va arriba a la derecha y «Administrar» abajo a ancho completo.
128
+ - `appearance="list"` no tiene borde ni fondo ni padding: va dentro de algo que los ponga.
129
+ - El host borra el atributo `title` del DOM; el título se ve como texto, no como `aria-label`.
130
+
131
+ ## Ejemplo
132
+
133
+ ```html
134
+ <sui-integration-switch
135
+ title="WhatsApp Business"
136
+ description="Avisa al cliente por WhatsApp cuando su pedido sale del local."
137
+ brandIconSrc="assets/brand/brand-logos/whatsapp-original.svg"
138
+ manage="Administrar"
139
+ [active]="whatsappActivo()"
140
+ (activeChange)="cambiarWhatsapp($event)"
141
+ (manageClick)="abrirAjustesWhatsapp()" />
142
+ ```