@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,178 @@
1
+ ---
2
+ name: sui-popover
3
+ selector: sui-popover
4
+ resumen: Panel con flecha que señala un elemento de la pantalla y explica algo nuevo, con una o dos acciones; sirve para recorridos de bienvenida
5
+ alternativas: [sui-tooltip (ayuda corta al pasar el mouse, sin acciones), sui-alert (aviso dentro del contenido), sui-modal (algo que pide decidir antes de seguir)]
6
+ ---
7
+
8
+ # sui-popover
9
+
10
+ Story: [Molecules/Popover](https://sui-design-system.web.app/?path=/docs/molecules-popover--docs)
11
+
12
+ Es **solo el panel**: no se abre solo, no se ancla al elemento ni se cierra al tocar afuera. Quien
13
+ lo usa decide cuándo mostrarlo y dónde ponerlo (un overlay del CDK, o posicionado a mano junto al
14
+ elemento). `position` solo mueve la flecha.
15
+
16
+ ## Cuándo usarlo / cuándo no
17
+
18
+ **Úsalo** para **señalar una parte de la pantalla** y decir algo que la persona todavía no sabe,
19
+ con un paso a seguir: el recorrido de bienvenida del panel del local la primera vez que entra, o
20
+ una función nueva que conviene mostrar donde está («Aquí programas tus pedidos»).
21
+
22
+ **No lo uses** para:
23
+
24
+ - La explicación de un ícono o un término sin acción: en web es `sui-tooltip`; en mobile, texto
25
+ visible o un `sui-modal-sheet`.
26
+ - Un aviso que no señala nada en particular: `sui-alert` o `sui-toast`.
27
+ - Pedir una decisión que bloquea («¿Cancelar el pedido?»): `sui-modal`.
28
+ - Un menú de opciones: `sui-dropdown-menu`.
29
+
30
+ ## Qué variante usar
31
+
32
+ > La guía del DS dice para qué es, pero no cuándo va cada pie ni cada posición. Lo marcado «según la
33
+ > guía del DS» sale de su texto; «canon» es lo ya aprobado en la app; el resto es **propuesta**.
34
+ >
35
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**, y ningún recorrido de
36
+ > bienvenida hecho a mano. Todo lo de abajo es para el primer flujo que lo necesite.
37
+
38
+ ### Lo que sí dice la guía del DS
39
+
40
+ - **Para qué es**: llamar la atención sobre **elementos específicos** de la interfaz.
41
+ - Se posiciona arriba, abajo o a los costados del elemento que señala.
42
+ - La guía no dice nada del pie con pasos; eso viene del Figma del DS (pie con puntos, con texto o
43
+ con dos botones a lo ancho).
44
+
45
+ ### `footer` — el pie
46
+
47
+ | footer | cuándo | fuente |
48
+ |---|---|---|
49
+ | `stretch` (default) | Un aviso suelto con dos salidas a lo ancho: «Ahora no» / «Probar». Con `doubleButton=false`, una sola: «Entendido». | propuesta |
50
+ | `stepper` | Un **recorrido de varios pasos**: los puntos dicen en cuál va y cuántos faltan. | propuesta |
51
+ | `none` | Solo informa y se cierra con la X: «Aquí ves lo que ganaste hoy». | propuesta |
52
+ | `text-stepper` | Recorridos de más de 5 pasos, donde los puntos ya no se cuentan: «Paso 3 de 7» (en español desde 03-10-2026). | propuesta |
53
+
54
+ ### Botones del pie
55
+
56
+ | prop | valor | cuándo | fuente |
57
+ |---|---|---|---|
58
+ | `doubleButton` | `true` (default) | Desde el segundo paso de un recorrido («Atrás» / «Siguiente»), o un aviso suelto con «Ahora no» / «Probar». | propuesta |
59
+ | `doubleButton` | `false` | El primer paso de un recorrido o un aviso suelto con una sola salida («Entendido»). | propuesta |
60
+ | `backLabel` · `nextLabel` | texto en español | Los defaults ya son «Atrás» y «Siguiente» (03-10-2026); el último paso pasa `nextLabel="Listo"`. | canon (copy de la app: tuteo, sentence case) |
61
+ | orden | secundario a la izquierda, primario a la derecha | Ya lo hace el componente (stroke neutral + filled primary), igual que `sui-modal-footer`. | canon (orden de botones de la app) |
62
+ | `step` | `{ current, total }` | Con `stepper`. Sin `step`, el componente asume **1 de 4**. | propuesta |
63
+
64
+ ### Flecha, ícono, texto y cierre
65
+
66
+ | prop | valor | cuándo | fuente |
67
+ |---|---|---|---|
68
+ | `tail` | `true` (default) | Siempre que señala algo: la flecha es lo que dice «es esto». | según la guía del DS (se posiciona junto al elemento) |
69
+ | `tail` | `false` | No usar hoy. **Cuándo sí:** la bienvenida inicial de un recorrido, centrada, sin elemento al que apuntar. | propuesta |
70
+ | `position` | `top-*` | El popover va **debajo** del elemento (la flecha arriba). Lo normal: el elemento sigue a la vista. | propuesta |
71
+ | `position` | `bottom-*` | El elemento está abajo (la barra de pestañas): el popover va encima. | propuesta |
72
+ | `position` | `left-*` · `right-*` | Elementos de una barra lateral (el sidebar del panel). | propuesta |
73
+ | `position` | `*-left` · `*-center` · `*-right` | Alinea la flecha con el elemento cuando el popover no puede centrarse sobre él (está cerca de un borde). | propuesta |
74
+ | `icon` | el de lo que se explica | Siempre uno que diga el tema (`calendar-schedule-line` para programar). El default es un ícono de persona. | propuesta |
75
+ | `icon` | `null` | Cuando el texto ya basta y el ícono sería decoración. | propuesta |
76
+ | `title` | frase corta | Qué es, en sentence case y sin punto final. | canon (copy de la app) |
77
+ | `description` | una o dos frases | Qué hace o por qué importa. | propuesta |
78
+ | `dismissIcon` | `true` (default) | Siempre: un recorrido se puede saltar. | propuesta |
79
+ | `dismissIcon` | `false` | No usar hoy. **Cuándo sí:** un paso que no se puede saltar porque sin él no funciona lo siguiente. | propuesta |
80
+ | `[data-slot=body]` | contenido propio | No usar hoy. **Cuándo sí:** el paso necesita una imagen o una lista en vez de título y texto. | propuesta |
81
+
82
+ ## Tamaño en mobile y en web
83
+
84
+ El panel mide **320 px** de ancho fijo: cabe en un teléfono de 360 px o más con 16 px de margen a
85
+ cada lado; en uno más angosto se sale. El `size` cambia **los botones del pie**:
86
+
87
+ | size | botones del pie | fuente |
88
+ |---|---|---|
89
+ | `small` (default) | 36 px (`sui-button` `small`) | Figma |
90
+ | `medium` | 40 px (`sui-button` `medium`) | canon (aprobado por el dueño, 03-10-2026): extensión SUI |
91
+
92
+ La X se ve de 24 y **se toca en 40 × 40** en los dos (un `::before` invisible sobre su botón,
93
+ extensión SUI 03-10-2026).
94
+
95
+ | uso | mobile | web | fuente |
96
+ |---|---|---|---|
97
+ | el panel | 320 px (cabe desde 352 px de pantalla) | 320 px | según el componente |
98
+ | botones del pie | `medium` (40) | `small` (36) | propuesta: el tamaño es extensión SUI aprobada (03-10-2026); el «cuándo» falta aprobarlo |
99
+ | la X | se ve de 24, se toca en 40 | igual | canon (aprobado por el dueño, 03-10-2026) |
100
+
101
+ ```html
102
+ <sui-popover footer="stretch" [size]="bp.isMobile() ? 'medium' : 'small'" … />
103
+ ```
104
+
105
+ En mobile, si el recorrido es la bienvenida de la app, conviene una pantalla o un
106
+ `sui-modal-sheet` por paso en vez del popover (propuesta, falta aprobación).
107
+
108
+ ## «No usar»: por qué no hoy y cuándo sí
109
+
110
+ Hoy el popover entero tiene **0 usos**; esto es lo que no se usaría aunque llegue un flujo nuevo.
111
+
112
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
113
+ |---|---|---|
114
+ | el componente | no hay recorridos ni funciones nuevas que señalar | el recorrido de bienvenida del panel del local; mostrar dónde está una función nueva |
115
+ | `footer="text-stepper"` | ningún recorrido largo hoy | recorridos de más de 5 pasos, donde los puntos ya no se cuentan («Paso 3 de 7») |
116
+ | `tail=false` | todo popover señala algo | la bienvenida inicial, centrada |
117
+ | `dismissIcon=false` | un recorrido siempre se puede saltar | un paso sin el que no funciona el siguiente |
118
+ | `[data-slot=body]` | título y texto alcanzan | un paso con imagen o lista |
119
+
120
+ ## API
121
+
122
+ <!-- api:inicio -->
123
+ <!-- generada: no editar a mano — `npm run docs` -->
124
+
125
+ | input | tipo | default |
126
+ |---|---|---|
127
+ | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right' \| 'right-top' \| 'right-center' \| 'right-bottom' \| 'left-top' \| 'left-center' \| 'left-bottom'` | `'top-left'` |
128
+ | `tail` | `boolean` | `true` |
129
+ | `icon` | `string \| null` | `'user-6-line'` |
130
+ | `title` | `string` | `''` |
131
+ | `description` | `string \| null` | `null` |
132
+ | `dismissIcon` | `boolean` | `true` |
133
+ | `footer` | `'none' \| 'stretch' \| 'text-stepper' \| 'stepper'` | `'stretch'` |
134
+ | `backLabel` | `string` | `'Atrás'` |
135
+ | `nextLabel` | `string` | `'Siguiente'` |
136
+ | `doubleButton` | `boolean` | `true` |
137
+ | `step` | `{ current: number; total: number } \| null` | `null` |
138
+ | `size` | `'small' \| 'medium'` | `'small'` |
139
+
140
+ | output | payload |
141
+ |---|---|
142
+ | `dismissed` | `void` |
143
+ | `back` | `void` |
144
+ | `next` | `void` |
145
+ <!-- api:fin -->
146
+
147
+ Slot: `[data-slot=body]` reemplaza título y descripción.
148
+
149
+ ## Trampas
150
+
151
+ - **Textos por defecto en español** (desde 03-10-2026): «Atrás», «Siguiente» y, en `text-stepper`,
152
+ «Paso X de N» (este último no se puede cambiar).
153
+ - **El ícono por defecto es una persona** (`user-6-line`). Si no quieres ícono, `[icon]="null"`.
154
+ - **`step` vacío = 1 de 4**: con `stepper` o `text-stepper`, pásalo siempre.
155
+ - **No se posiciona solo**: `position` cambia dónde sale la flecha, no dónde aparece el panel.
156
+ Tampoco se cierra al tocar afuera ni con Escape; cierra quien escucha `(dismissed)`.
157
+ - `position` se lee «borde de la flecha, luego su lugar»: `top-left` es flecha **arriba a la
158
+ izquierda**, o sea, el popover queda **debajo** del elemento.
159
+ - El host lleva `role="dialog"` con `aria-label` = `title` y borra el `title` nativo.
160
+ - Los botones del pie no tienen `disabled`: si un paso no puede avanzar, se maneja desde `(next)`.
161
+ - **`stretch` reparte el ancho entre los dos botones** y en los steppers miden 80 px como mínimo
162
+ (crecen si el texto no entra). Hasta el 03-10-2026 no pasaba: el `.scss` apuntaba a
163
+ `.sui-popover__btn` y el template pone `.sui-popover__action`.
164
+
165
+ ## Ejemplo
166
+
167
+ ```html
168
+ <!-- Paso 2 de 3 de un recorrido -->
169
+ <sui-popover position="top-left" icon="calendar-schedule-line"
170
+ title="Programa tus pedidos" description="Elige el día y la hora, y el pedido entra solo a la cocina."
171
+ footer="stepper" [step]="{ current: 2, total: 3 }"
172
+ backLabel="Atrás" nextLabel="Siguiente"
173
+ (back)="anterior()" (next)="siguiente()" (dismissed)="saltar()" />
174
+
175
+ <!-- Aviso suelto con una sola salida -->
176
+ <sui-popover position="bottom-center" icon="wallet-3-line" title="Aquí ves lo que ganaste hoy"
177
+ [doubleButton]="false" nextLabel="Entendido" (next)="cerrar()" (dismissed)="cerrar()" />
178
+ ```
@@ -0,0 +1,173 @@
1
+ ---
2
+ name: sui-product-card
3
+ selector: sui-product-card
4
+ resumen: Tarjeta de un producto de la carta con foto, nombre, descripción, precio y el botón para agregar (o editar)
5
+ alternativas: [sui-store-card (un local, no un producto), sui-card (cualquier otro contenido), sui-content-card (un producto en una fila, sin foto grande)]
6
+ ---
7
+
8
+ # sui-product-card
9
+
10
+ Story: [Molecules/Product Card](https://sui-design-system.web.app/?path=/docs/molecules-product-card--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para cada producto en una grilla de la carta: la carta del local que ve el cliente y la
15
+ grilla de productos que ve el dueño del local (con el lápiz).
16
+
17
+ **No lo uses** para:
18
+
19
+ - Un producto dentro del carrito, del checkout o del detalle de un pedido: ahí va una fila (sin
20
+ foto grande, con cantidad y subtotal).
21
+ - Un local: `sui-store-card`.
22
+ - Una promo o una campaña: tienen su propio carrusel.
23
+
24
+ Regla corta: **producto en la grilla de la carta → product-card · producto en una lista de pedido
25
+ → fila.**
26
+
27
+ ## Qué variante usar
28
+
29
+ > **La guía del DS no tiene página de la product card.** Es un componente propio de SUI, portado
30
+ > de la carta de la app. La card **dibuja, no decide**: el precio, si se vende ahora y la
31
+ > etiqueta de no disponible le llegan ya resueltos.
32
+ >
33
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-card`): **3
34
+ > product-cards en 2 archivos**: la carta del cliente
35
+ > (`pages/customer/restaurant-menu/restaurant-menu.html:324` y `:387`) y la grilla del local
36
+ > (`pages/products/products.html:145`, con `action="edit"`).
37
+
38
+ ### Las piezas
39
+
40
+ | prop | cuándo | fuente |
41
+ |---|---|---|
42
+ | `name`, `price` (obligatorios) | Siempre. El precio sale con 2 decimales y punto: «S/ 35.00». | propuesta |
43
+ | `description` | Una línea que diga qué trae: «Con hierbas aromáticas y papas nativas». | propuesta |
44
+ | `image` como objeto `{ url, fallbackUrl, position }` | Lo normal: `url` = la miniatura, `fallbackUrl` = la foto grande por si la miniatura no carga, `position` = encuadre vertical guardado. | propuesta (así lo hace la carta) |
45
+ | `image` como string | Solo la url, sin respaldo. | propuesta |
46
+ | `image = null` + `fallback` | Sin foto: banda de color con el nombre (default). La carta pasa `{ color }` para variar el color por visita con `SUI_PRODUCT_FALLBACK_COLORS`. | propuesta |
47
+ | `available=false` + `unavailableLabel` | Agotado o fuera de horario; la etiqueta dice cuál («Agotado», «Desde las 12 m.»). Se atenúa y pierde el «+». | propuesta |
48
+ | `quantity` | Cuántos hay en el carrito: con más de 0, el «+» pasa a stepper (− / cantidad / +). | propuesta |
49
+ | `closed` | El local no recibe pedidos: se ve, no se agrega. | propuesta |
50
+ | `action="add"` (default) | La carta del cliente. | propuesta |
51
+ | `action="edit"` | La grilla del dueño: lápiz siempre (también en agotado, que es justo el que quiere tocar), nunca stepper. | propuesta |
52
+ | `currencySymbol` | Default `S/`. No se cambia: la app cobra en soles. | propuesta |
53
+ | `fallback.variant="icon"` | Ver «No usar». | propuesta |
54
+
55
+ Outputs: `openDetail` (toque en la card), `add`, `increment`, `decrement`, `edit`.
56
+
57
+ **Usos de hoy que rompen las reglas:** 0. **Falta en el DS:** el precio tachado y la chapa de
58
+ descuento. La carta los dibuja afuera (`pages/customer/restaurant-menu/restaurant-menu.scss:426`,
59
+ deuda anotada): cuando se pueda publicar, va un input `precioLista`.
60
+
61
+ ## Tamaño en mobile y en web
62
+
63
+ El ancho lo da la grilla y la card llena su celda. El `size` cambia **los botones del pie**
64
+ («+», lápiz y stepper), en la escala del `sui-button`:
65
+
66
+ | size | «+» / lápiz | stepper (cada lado) | glyph | fuente |
67
+ |---|---|---|---|---|
68
+ | `small` (default) | 36 × 36 | 34 × 36 | 18 / 16 | lo que había en el DS |
69
+ | `medium` | 40 × 40 | 40 × 40 (pill de 104) | 20 | canon (aprobado por el dueño, 03-10-2026): extensión SUI |
70
+
71
+ En `medium` el stepper (104 px) no entra al lado del precio en la grilla de 2 columnas de mobile:
72
+ el pie envuelve y el stepper baja a su propia línea, a la derecha. El «+» y el lápiz sí siguen al
73
+ lado del precio.
74
+
75
+ | uso | mobile | web | fuente |
76
+ |---|---|---|---|
77
+ | grilla de la carta | 2 columnas (`1fr 1fr`, gap 12) | `repeat(auto-fill, minmax(220px, 1fr))` desde 600 px y `minmax(180px, 1fr)` en desktop | lo que hay en la app |
78
+ | botón «+», lápiz, stepper | `medium` (40) | `small` (36) | propuesta: el tamaño es extensión SUI aprobada (03-10-2026); el «cuándo» falta aprobarlo |
79
+
80
+ ```html
81
+ <sui-product-card [size]="bp.isMobile() ? 'medium' : 'small'" … />
82
+ ```
83
+
84
+ ## Precio con descuento
85
+
86
+ `precioLista` es el precio de referencia. Si es **mayor** que `price`, la card pinta el precio en
87
+ rojo con la chapa del ahorro al lado (`-25%`, calculada: no se pasa) y el de referencia tachado
88
+ debajo. Igual o menor, o `null`, se ignora. Rojo `--sui-red-700` (5.26:1 sobre blanco, AA) y en
89
+ oscuro `--sui-red-400`; no `--sui-state-error-base`, que da 3.66:1. Propuesta, falta aprobación
90
+ del dueño: reemplaza el bloque que la app dibuja afuera en `restaurant-menu.scss` (precio tachado
91
+ por `::after` y chapa por `::before` sobre `.product__price`), que se borra al migrar.
92
+
93
+ ## «No usar»: por qué no hoy y cuándo sí
94
+
95
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
96
+ |---|---|---|
97
+ | `fallback: { variant: 'icon' }` | la banda con el nombre dice más que un ícono | una carta de códigos (repuestos, ferretería) donde el nombre no ayuda a reconocerlo |
98
+ | `currencySymbol` distinto de `S/` | la app solo cobra en soles | si algún día se cobra en dólares |
99
+
100
+ ## API
101
+
102
+ <!-- api:inicio -->
103
+ <!-- generada: no editar a mano — `npm run docs` -->
104
+
105
+ | input | tipo | default |
106
+ |---|---|---|
107
+ | `name` | `string` | — (requerido) |
108
+ | `description` | `string` | `''` |
109
+ | `price` | `number` | — (requerido) |
110
+ | `image` | `SuiProductCardImage \| string \| null` | `null` |
111
+ | `available` | `boolean` | `true` |
112
+ | `quantity` | `number` | `0` |
113
+ | `closed` | `boolean` | `false` |
114
+ | `currencySymbol` | `string` | `'S/'` |
115
+ | `fallback` | `SuiProductCardFallback \| null` | `null` |
116
+ | `unavailableLabel` | `string` | `'Agotado'` |
117
+ | `action` | `'add' \| 'edit'` | `'add'` |
118
+ | `precioLista` | `number \| null` | `null` |
119
+ | `size` | `'small' \| 'medium'` | `'small'` |
120
+
121
+ | output | payload |
122
+ |---|---|
123
+ | `openDetail` | `void` |
124
+ | `add` | `void` |
125
+ | `increment` | `void` |
126
+ | `decrement` | `void` |
127
+ | `edit` | `void` |
128
+ <!-- api:fin -->
129
+
130
+ ```ts
131
+ interface SuiProductCardImage { url: string; fallbackUrl?: string; position?: number; }
132
+ interface SuiProductCardFallback { variant?: 'name' | 'icon'; color?: string | null; }
133
+ ```
134
+
135
+ `SUI_PRODUCT_FALLBACK_COLORS` (exportada): los colores `--sui-state-*-base` para la banda.
136
+
137
+ ## Trampas
138
+
139
+ - **El host lleva `content-visibility: auto`**: las cards fuera de pantalla no se pintan. Al
140
+ imprimir ya no pasa (el componente lo apaga en `@media print`). En una captura de página
141
+ completa (shot-scraper `--full`) sí pueden salir en blanco: el opt-out es
142
+ `sui-product-card { content-visibility: visible; }` desde afuera.
143
+ - **Necesita una celda con alto**: el host es `height: 100%` y la foto usa `NgOptimizedImage`
144
+ `fill`. En un contenedor sin alto la card colapsa; ponla en una grilla.
145
+ - **La card es un `<div>`, no un `<button>`** (desde 03-10-2026): lo que abre el detalle es
146
+ `.product__hit`, un botón invisible estirado sobre toda la card con el nombre como
147
+ `aria-label`; los botones del pie van por encima (`z-index` 2). Así no hay un botón dentro de
148
+ otro. Un estilo de afuera que apunte a `button.product` ya no engancha.
149
+ - **La app pinta hoy el descuento por fuera** con `::before`/`::after` sobre `.product__price`: si
150
+ se pasa `precioLista` hay que borrar ese bloque en el mismo cambio, o salen dos tachados.
151
+ - Con `quantity === 1` el «−» pasa a basurero («Quitar del carrito»).
152
+ - El color sin foto es determinista por nombre: el mismo producto siempre sale del mismo color,
153
+ salvo que pases `fallback.color`.
154
+ - Cada url nueva vuelve a mostrar el barrido de carga; si la miniatura falla, intenta con
155
+ `fallbackUrl` antes de apagarlo.
156
+
157
+ ## Ejemplo
158
+
159
+ ```html
160
+ <sui-product-card
161
+ [name]="p.name"
162
+ [description]="p.description ?? ''"
163
+ [price]="p.price"
164
+ [image]="p.thumbnailUrl ? { url: p.thumbnailUrl, fallbackUrl: p.imageUrl, position: p.imagePosition ?? 50 } : null"
165
+ [available]="disponibleAhora(p)"
166
+ [unavailableLabel]="etiquetaNoDisponible(p)"
167
+ [quantity]="cantidadEnCarrito(p)"
168
+ [closed]="noSePuedePedir()"
169
+ (openDetail)="abrirDetalle(p)"
170
+ (add)="agregar(p)"
171
+ (increment)="sumar(p)"
172
+ (decrement)="restar(p)" />
173
+ ```
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: sui-radio-card
3
+ selector: sui-radio-card
4
+ resumen: Opción con tarjeta (ícono o foto, título, descripción y el radio) para elegir UNA de una lista corta
5
+ alternativas: [sui-radio-label (la misma elección sin tarjeta, en un formulario denso), sui-select (lista larga o que no entra en pantalla), sui-segmented-control (2–4 vistas de la misma pantalla), sui-checkbox-card (se pueden elegir varias), sui-switch-card (se prende o se apaga un ajuste)]
6
+ ---
7
+
8
+ # sui-radio-card
9
+
10
+ Story: [Molecules/Card/Radio Card](https://sui-design-system.web.app/?path=/docs/molecules-card-radio-card--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para elegir una sola opción de una lista corta cuando cada opción merece explicarse:
15
+ «Delivery / Recojo en tienda», cómo cobrar («A mi saldo / Por Yape»), el medio de pago, el motivo
16
+ de una cancelación, el modo de asignación de repartidores.
17
+
18
+ **No lo uses** para:
19
+
20
+ - Varias opciones que se suman: `sui-checkbox-card`.
21
+ - Un ajuste que se prende o se apaga: `sui-switch-card`.
22
+ - Una lista larga (más de 6–7 opciones) o que crece con los datos: `sui-select`.
23
+ - Cambiar de vista dentro de la misma pantalla: `sui-segmented-control` o tabs.
24
+
25
+ Regla corta: **una de pocas, con explicación → radio-card · una de muchas → select · varias →
26
+ checkbox-card.**
27
+
28
+ ## Qué variante usar
29
+
30
+ > **La guía del DS no tiene página de la radio card.** De la guía sale solo lo del radio que lleva
31
+ > adentro: **«para elegir una sola opción de una lista corta»**. El resto es práctica de la app
32
+ > («propuesta») o regla ya aprobada («canon»).
33
+ >
34
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-card`): **31
35
+ > radio-cards en 14 archivos**. Tipos: 23 `left-icon` (3 de ellos con emoji animado por
36
+ > `iconSrc`), 6 `basic`, 1 `avatar`, 1 `company`. Tamaños: 24 `bp.controlSize()`, 4 `medium`, 1
37
+ > `xlarge` fijo, 1 dinámico, 1 sin `size`. 6 con `sublabel`, 3 con `disabled`, 1 con `badge`.
38
+
39
+ ### `type` (el leading)
40
+
41
+ | type | cuándo | fuente |
42
+ |---|---|---|
43
+ | `left-icon` con `icon` | Lo normal: un ícono Remix que dice de qué va la opción (`truck-line` Delivery, `store-2-line` Recojo). | propuesta (práctica de la app) |
44
+ | `left-icon` con `iconSrc` | Emoji animado cuando la elección es de las grandes del rol (el vehículo del repartidor, el rol al registrarse, el tipo de mandadito). Los emojis van de `assets/emojis/` o `FLUENT_EMOJI_ANIM`. | propuesta |
45
+ | `left-icon` con `emoji` | Lo mismo con un nombre de `SuiEmoji` en vez de una URL. Hoy 0 usos; vale igual. | propuesta |
46
+ | `basic` | Opciones de texto puro: motivos de cancelación, quién paga el envío, los premios de una meta. | propuesta |
47
+ | `avatar` (`imageUrl`) | El logo redondo de un medio de pago (Yape, Plin, efectivo en el checkout). | propuesta |
48
+ | `company` (`imageUrl`) | Elegir entre tus locales (cambiar de negocio en mi-negocio). | propuesta |
49
+ | `brand`, `card-provider` | Ver «No usar». | propuesta |
50
+
51
+ ### Las piezas
52
+
53
+ | prop | cuándo | fuente |
54
+ |---|---|---|
55
+ | `value` (obligatorio) | Siempre, aunque no haya grupo. Es lo que emite `selected`. | propuesta |
56
+ | `label` | Siempre. El nombre de la opción, corto: «Delivery», «Lo antes posible». | propuesta |
57
+ | `description` | Casi siempre: la consecuencia de elegirla («Lo revisamos y te transferimos. Puede tardar unas horas.»). | propuesta |
58
+ | `sublabel` | Una precisión corta entre paréntesis («(canal estable)», «(sin OTA)»). Nunca la explicación: eso es `description`. | propuesta |
59
+ | `badge` | Marca una opción frente a las otras: «Por defecto». Con string sale azul `light`; pasa el objeto con el color que corresponda según el badge (un «Por defecto» es `gray`). | propuesta |
60
+ | `disabled` | La opción existe pero hoy no se puede: di por qué en la `description` («El local está cerrado ahora»). | propuesta |
61
+ | `checked` / `[(ngModel)]` / `sui-radio-group` | El estado: `[checked]` + `(selected)` sueltos (lo normal en la app), o `ngModel` con `name` (checkout), o dentro de un `sui-radio-group`. | propuesta |
62
+
63
+ ### `size` (el radio de adentro)
64
+
65
+ | size | cuándo | fuente |
66
+ |---|---|---|
67
+ | `xlarge` (28) | Mobile, vía `bp.controlSize()`. | canon (aprobado por el dueño, 28-09-2026: «componentes grandes») |
68
+ | `large` (24) | Desktop, vía `bp.controlSize()`. | canon (aprobado por el dueño, 28-09-2026) |
69
+ | `medium` (20, default) | No usar en pantallas nuevas. | canon (aprobado por el dueño, 28-09-2026) |
70
+
71
+ **Usos de hoy que rompen las reglas:**
72
+
73
+ - **5 sin el size del breakpoint**: `components/por-cobrar-card/por-cobrar-card.html:38` y `:48`,
74
+ `pages/driver/driver-dashboard/driver-dashboard.html:581`,
75
+ `pages/super-admin/master-orders/master-orders.html:720` (los cuatro en `medium`) y
76
+ `pages/mi-negocio/mi-negocio.html:22` (sin `size`). Además `pages/customer/mandadito/mandadito.html:30`
77
+ va en `xlarge` fijo (en web debería ser `large`).
78
+ - **A revisar:** `pages/super-admin/master-orders/master-orders.html:720` lista a los repartidores
79
+ candidatos, una lista que crece con la flota (con muchos, `sui-select` o una lista con búsqueda).
80
+ `pages/role-selector/role-selector.html:24` usa la radio como botón: elegir ya avanza, sin
81
+ `[checked]`.
82
+
83
+ ## Tamaño en mobile y en web
84
+
85
+ La card entera es el área táctil (≥ 72 de alto con descripción, 16 de padding): llega al piso de
86
+ 44 sola. El `size` cambia solo el radio.
87
+
88
+ | uso | mobile | web |
89
+ |---|---|---|
90
+ | toda radio-card | `xlarge` (radio 28) | `large` (radio 24) |
91
+
92
+ ```html
93
+ <sui-radio-card [size]="bp.controlSize()" … /> <!-- isMobile() ? 'xlarge' : 'large' -->
94
+ ```
95
+
96
+ ## «No usar»: por qué no hoy y cuándo sí
97
+
98
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
99
+ |---|---|---|
100
+ | `type="brand"` (0 usos) | no hay servicios de terceros que elegir | elegir el canal por el que te contactamos (WhatsApp, correo) con su logo (`brand`) |
101
+ | `type="card-provider"` (0 usos) | no hay pago con tarjeta guardada | elegir una tarjeta guardada en el checkout, cuando entre Culqi / Niubiz |
102
+ | `size="medium"` | el radio de 20 se pierde en el teléfono | una tabla densa solo de desktop |
103
+
104
+ ## API
105
+
106
+ <!-- api:inicio -->
107
+ <!-- generada: no editar a mano — `npm run docs` -->
108
+
109
+ | input | tipo | default |
110
+ |---|---|---|
111
+ | `type` | `'basic' \| 'left-icon' \| 'avatar' \| 'card-provider' \| 'brand' \| 'company'` | `'basic'` |
112
+ | `size` | `'medium' \| 'large' \| 'xlarge'` | `'medium'` |
113
+ | `value` | `string` | — (requerido) |
114
+ | `label` | `string` | `''` |
115
+ | `sublabel` | `string \| null` | `null` |
116
+ | `description` | `string \| null` | `null` |
117
+ | `icon` | `string \| null` | `null` |
118
+ | `emoji` | `SuiEmojiName \| null` | `null` |
119
+ | `iconSrc` | `string \| null` | `null` |
120
+ | `imageUrl` | `string \| null` | `null` |
121
+ | `brand` | `SuiBrandLogoCompany \| null` | `null` |
122
+ | `badge` | `SuiBadgeConfig \| string \| null` | `null` |
123
+ | `disabled` | `boolean` | `false` |
124
+ | `checked` · model, `[(checked)]` | `boolean` | `false` |
125
+
126
+ | output | payload |
127
+ |---|---|
128
+ | `selected` | `string` |
129
+ <!-- api:fin -->
130
+
131
+ Implementa `ControlValueAccessor` (el valor del form es el `value` elegido). Dentro de un
132
+ `sui-radio-group`, el grupo decide cuál está marcada.
133
+
134
+ ## Trampas
135
+
136
+ - **`value` es obligatorio** aunque la uses suelta: sin él Angular tira error de input requerido.
137
+ - **Precedencia del leading en `left-icon`: `icon` > `emoji` > `iconSrc`.** Si pasas `icon` y
138
+ `iconSrc`, gana el ícono.
139
+ - Con `iconSrc` el círculo pasa a modo «relleno» (sin padding): el emoji ocupa los 40 × 40.
140
+ - `avatar`, `company` y `card-provider` leen la imagen de **`imageUrl`**; `brand` lee `brand` (o
141
+ `imageUrl` si la marca no está en el catálogo). No hay slot para proyectar el leading (a
142
+ diferencia de `sui-checkbox-card`).
143
+ - Suelta (sin grupo), elegirla la marca pero **no desmarca a las otras**: el padre tiene que
144
+ bindear `[checked]` de todas a la misma señal.
145
+ - `badge` con string sale **azul `light` `small`** (en `sui-content-label` sale naranja).
146
+ - `size` cambia solo el radio, no el padding ni el alto de la card.
147
+
148
+ ## Ejemplo
149
+
150
+ ```html
151
+ <sui-radio-card
152
+ [size]="bp.controlSize()"
153
+ type="left-icon"
154
+ value="pickup"
155
+ icon="store-2-line"
156
+ label="Recojo en tienda"
157
+ description="Lo recoges en el local"
158
+ [checked]="modo() === 'pickup'"
159
+ (selected)="modo.set('pickup')" />
160
+ ```
@@ -90,11 +90,11 @@ lista cargada, que viene con tope. La fórmula depende de `n`: otro denominador
90
90
  | `reviewCount` | número | **No usar** (ver abajo). | propuesta, falta aprobación |
91
91
  | `emojis` | lista | Dejar el default (cinco caras, de triste a feliz). Cambiarlo rompe la lectura de la escala. | propuesta, falta aprobación |
92
92
  | `ariaLabel` | texto | Decir qué se califica: «Califica al repartidor», «Nota del local». El default es «Calificación». | propuesta, falta aprobación |
93
- | `reason` / `reasonPlaceholder` / `reasonRows` | — | Solo en `bar-area`. **El placeholder va siempre en español**: el default está en inglés. | propuesta, falta aprobación |
93
+ | `reason` / `reasonPlaceholder` / `reasonRows` | — | Solo en `bar-area`. El placeholder por defecto es «Cuéntanos por qué» (03-10-2026). | propuesta, falta aprobación |
94
94
 
95
95
  **Usos de hoy que rompen la regla:**
96
96
 
97
- - **1 `reviewCount`** (pinta «Ratings» en inglés):
97
+ - **1 `reviewCount`** (hasta el 03-10-2026 pintaba «Ratings» en inglés; ahora «calificaciones»):
98
98
  `pages/super-admin/master-users/master-user-sheet.html:142`.
99
99
  - **1 rating usado como ícono** (`type="heart"` con `max=1` para los favoritos):
100
100
  `components/ratings-detail/ratings-detail.html:45`. Va `sui-icon name="heart-3-fill"`.
@@ -110,7 +110,7 @@ lista cargada, que viene con tope. La fórmula depende de `n`: otro denominador
110
110
  | `type="heart"` | La app califica con estrellas; el corazón es favoritos. | «¿Cuánto te gustó?» de una campaña, donde una estrella se leería como la nota del local. |
111
111
  | `type="number"` | No hay encuestas de 1 a 10. | «¿Qué tan probable es que nos recomiendes?» con `max=10` (ocupa 640 px de ancho). |
112
112
  | `showValue` | Escribe «4.7» con punto; la app escribe «4,7». | Si el DS formatea en `es-PE`. |
113
- | `reviewCount` | Agrega la palabra «Ratings» en inglés y abrevia «1.2K». | Si el DS traduce la etiqueta. |
113
+ | `reviewCount` | Abrevia «1.2K calificaciones» (la etiqueta está en español desde 03-10-2026). | Una lista de locales donde el conteo exacto no importa. |
114
114
  | `alignment="vertical"` | Solo ordena `showValue` y `reviewCount`, que no se usan. | Junto con esos dos, si se arreglan. |
115
115
 
116
116
  ## Tamaño en mobile y en web
@@ -160,7 +160,7 @@ plataforma solo se elige en `inline`.
160
160
  | `emojis` | `string[]` | `[ 'pensive-face', 'confused-face', 'neutral-face', 'slightly-smiling-face', 'grinning-face-smiling-eyes', ]` |
161
161
  | `ariaLabel` | `string` | `'Calificación'` |
162
162
  | `reason` · model, `[(reason)]` | `string` | `''` |
163
- | `reasonPlaceholder` | `string` | `'Tell us why!'` |
163
+ | `reasonPlaceholder` | `string` | `'Cuéntanos por qué'` |
164
164
  | `reasonRows` | `number` | `3` |
165
165
 
166
166
  | output | payload |
@@ -175,8 +175,9 @@ Implementa `ControlValueAccessor` sobre `value` (sirve con `formControlName`). `
175
175
 
176
176
  - **`showValue`, `reviewCount` y `reviewLink` solo se ven con `alignment="horizontal"` o
177
177
  `"vertical"`.** Con el default `inline` no aparecen.
178
- - **`reviewCount` agrega «Ratings» en inglés** y `showValue` formatea con punto.
179
- - **`reasonPlaceholder` viene en inglés** («Tell us why!»).
178
+ - **`reviewCount` agrega «calificaciones»** (en singular con 1; antes «Ratings») y `showValue`
179
+ formatea con punto.
180
+ - **`reasonPlaceholder` por defecto dice «Cuéntanos por qué»** (hasta el 03-10-2026, «Tell us why!»).
180
181
  - **`halfStep` solo funciona en `inline` + `readonly`.** En un input no hay medias.
181
182
  - **En `bar` el `size` no hace nada** (íconos de 20, celdas de 64); en `cell` los íconos miden 32.
182
183
  - **En `bar` y `bar-area` se pinta solo la celda elegida**, no las anteriores: no es una fila de