@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,114 @@
1
+ ---
2
+ name: sui-rich-editor
3
+ selector: sui-rich-editor
4
+ resumen: Barra de formato (negrita, cursiva, alineación, enlace) con un área de texto editable opcional
5
+ alternativas: [sui-textarea (todo texto libre de la app hoy, sin formato)]
6
+ ---
7
+
8
+ # sui-rich-editor
9
+
10
+ Story: [Molecules/Rich Editor](https://sui-design-system.web.app/?path=/docs/molecules-rich-editor--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** solo si un texto **necesita formato** y se va a mostrar con ese formato: negritas,
15
+ listas o enlaces en un texto largo que se publica en la web.
16
+
17
+ **No lo uses** para:
18
+
19
+ - Notas, motivos, descripciones de productos, mensajes y avisos: `sui-textarea`. Todo eso se
20
+ guarda y se muestra como texto plano.
21
+ - Un push o un aviso del celular: no muestran formato.
22
+
23
+ Regla corta: **hoy, siempre `sui-textarea`.**
24
+
25
+ ## Qué variante usar
26
+
27
+ > **La guía del DS no tiene página para este componente.** Viene del Figma («Rich Editor»), donde
28
+ > solo está la barra de formato. Todo lo de abajo es **propuesta, falta aprobación**.
29
+ >
30
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **0 usos**.
31
+
32
+ | prop / evento | cuándo | fuente |
33
+ |---|---|---|
34
+ | `layout="full"` (default) | Título, tamaño, B I U S, alineación, comentario, enlace, mención. | API |
35
+ | `layout="formatting"` | Solo B I U S y alineación: lo único que el editor aplica solo. | propuesta, falta aprobación |
36
+ | `layout="compact"` | Solo título y tamaño (los dos son avisos a la pantalla, no formato real). | — |
37
+ | `layout="social"` | Comentario, enlace, mención (también avisos). | — |
38
+ | `showMore` | El botón ⋮ al final. Apagarlo si no hay más acciones. | API |
39
+ | `editable` | `true` para tener el área donde se escribe; `false` deja solo la barra. | API |
40
+ | `placeholder` | «Escribe algo…» (default) o algo del contexto. | API |
41
+ | `value` | El contenido, **en HTML**. | API |
42
+ | `headingLabel` · `fontSizeLabel` | Lo que dicen los botones de título y tamaño («Header», «14px» por default). | API |
43
+ | `(action)` | Cada botón de la barra: la pantalla tiene que resolver título, tamaño, enlace, comentario y mención. | API |
44
+
45
+ ## Tamaño en mobile y en web
46
+
47
+ **No tiene `size`.** Los botones de la barra son de 28 px: **no tiene tamaño suficiente para
48
+ mobile**. Si alguna vez se usa, que sea en el panel web.
49
+
50
+ ## «No usar»: por qué no hoy y cuándo sí
51
+
52
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
53
+ |---|---|---|
54
+ | el componente entero (0 usos) | ningún texto de la app lleva formato | una página de ayuda o de términos que se escribe desde el super admin y se muestra en la web |
55
+ | `layout` `full`, `compact`, `social` | título, tamaño, enlace, comentario y mención no los hace el editor: solo avisa | si se monta un editor completo (Tiptap) detrás |
56
+
57
+ ## API
58
+
59
+ <!-- api:inicio -->
60
+ <!-- generada: no editar a mano — `npm run docs` -->
61
+
62
+ ### `sui-rich-editor`
63
+
64
+ | input | tipo | default |
65
+ |---|---|---|
66
+ | `layout` | `'full' \| 'compact' \| 'formatting' \| 'social'` | `'full'` |
67
+ | `showMore` | `boolean` | `true` |
68
+ | `editable` | `boolean` | `false` |
69
+ | `placeholder` | `string` | `'Escribe algo…'` |
70
+ | `value` · model, `[(value)]` | `string` | `''` |
71
+ | `headingLabel` | `string` | `'Header'` |
72
+ | `fontSizeLabel` | `string` | `'14px'` |
73
+
74
+ | output | payload |
75
+ |---|---|
76
+ | `action` | `SuiRichEditorAction` |
77
+ | `valueChange` | `string` |
78
+
79
+ ### `sui-rich-editor-item`
80
+
81
+ | input | tipo | default |
82
+ |---|---|---|
83
+ | `type` | `'text' \| 'dropdown' \| 'icon' \| 'color'` | `'icon'` |
84
+ | `state` | `'default' \| 'hover' \| 'active'` | `'default'` |
85
+ | `label` | `string` | `''` |
86
+ | `icon` | `string` | `''` |
87
+ | `color` | `'gray' \| 'blue' \| 'orange' \| 'red' \| 'green' \| 'yellow' \| 'purple' \| 'sky' \| 'pink' \| 'teal'` | `'gray'` |
88
+ | `colorHex` | `string \| null` | `null` |
89
+ | `ariaLabel` | `string` | `''` |
90
+ | `disabled` | `boolean` | `false` |
91
+
92
+ | output | payload |
93
+ |---|---|
94
+ | `clicked` | `void` |
95
+ <!-- api:fin -->
96
+
97
+ Sub-componente: `sui-rich-editor-item` (cada botón de la barra).
98
+
99
+ ## Trampas
100
+
101
+ - **`value` es HTML.** Mostrarlo con `[innerHTML]` sin limpiarlo abre la puerta a código ajeno.
102
+ Limpiarlo en el servidor antes de guardar.
103
+ - **Solo B I U S y alineación funcionan solos** (usa `document.execCommand`, que está en desuso).
104
+ Título, tamaño, enlace, comentario y mención solo emiten `(action)`: la pantalla tiene que hacer
105
+ el resto.
106
+ - Por default muestra solo la barra: sin `[editable]="true"` no hay dónde escribir.
107
+ - Labels por default en inglés o técnicos («Header», «14px»).
108
+
109
+ ## Ejemplo
110
+
111
+ ```html
112
+ <sui-rich-editor layout="formatting" [showMore]="false" [editable]="true"
113
+ placeholder="Escribe la respuesta…" [(value)]="html" />
114
+ ```
@@ -72,6 +72,9 @@ Regla corta: **2-4 modos de la misma vista, efecto inmediato → segmented · se
72
72
  Alto de cada segmento: `small` 28 px, `medium` 28 px, `large` 32 px, `xlarge` 40 px (el control
73
73
  suma 8 px de borde interno).
74
74
 
75
+ En large y xlarge el texto sube a 16 px (regla del 03-10-2026): Label/Medium 16/24. En `large` el
76
+ padding del segmento con texto bajó de 6 a 4 para que siga midiendo 32.
77
+
75
78
  | uso | mobile | web |
76
79
  |---|---|---|
77
80
  | modos con texto (local del horario, rol) | `xlarge` (segmento de 40 px) | `medium` |
@@ -0,0 +1,183 @@
1
+ ---
2
+ name: sui-select
3
+ selector: sui-select
4
+ resumen: Campo que despliega una lista para elegir UNA opción (distrito, repartidor, categoría, motivo)
5
+ alternativas: [sui-radio-label / sui-radio-card (2 a 6 opciones fijas a la vista), sui-segmented-control (2 a 4 modos que se aplican al instante), sui-compact-select (filtro chico que se ajusta al texto), sui-compact-select-for-input (elegir moneda o país dentro de un campo), sui-tag-input (varios valores)]
6
+ ---
7
+
8
+ # sui-select
9
+
10
+ Story: [Molecules/Select/Select](https://sui-design-system.web.app/?path=/docs/molecules-select-select--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** para elegir **una** opción de una lista que **sale de datos** (departamento, provincia,
15
+ distrito, repartidor, categoría, plan) o que tiene **más de 6 opciones fijas**.
16
+
17
+ **No lo uses** para:
18
+
19
+ - 2 a 6 opciones fijas dentro de un formulario: se ven todas de una con `sui-radio-label` (web) o
20
+ `sui-radio-card` (mobile). Abrir un menú para elegir entre «Casa» y «Trabajo» es un toque de más.
21
+ - 2 a 4 modos de la vista que se aplican al tocar (Hoy · Semana · Mes): `sui-segmented-control`.
22
+ - **Buscar** dentro de la lista: `sui-select` **no tiene buscador**. Con 40 distritos se scrollea.
23
+ - Elegir **varias**: no es múltiple. Varias opciones fijas → casillas; valores libres → `sui-tag-input`.
24
+ - Una lista con foto o bandera por opción: las opciones solo muestran texto (ver «Trampas»).
25
+
26
+ Regla corta: **sale de datos o son más de 6 → select · 2–6 fijas → radio · modos de la vista → segmented.**
27
+
28
+ ## Qué variante usar
29
+
30
+ > **La guía del DS lo define como el control para elegir una sola opción de un menú** y muestra
31
+ > label y hint, íconos, tamaños, país, método de pago, usuario y deshabilitado. Trae además tres
32
+ > variantes que en SUI son componentes aparte: `compact` (`sui-compact-select`), `inline`
33
+ > (`sui-inline-select`) y `compactForInput` (`sui-compact-select-for-input`). No dice cuándo va cada una.
34
+ >
35
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-badge`, más
36
+ > `master-ejercicios`): **57 usos en 26 archivos**. Todos `type="basic"` y sin ícono. Tamaños:
37
+ > 39 sin `size` (`medium`), 11 `large`, 3 `bp.isMobile() ? 'large' : 'small'`, 2 `medium`,
38
+ > 1 `small`, 1 `bp.controlSize()`. 29 pasan `placeholder`.
39
+
40
+ ### Qué elegir según las opciones
41
+
42
+ | caso | componente | fuente |
43
+ |---|---|---|
44
+ | La lista sale de datos (ubigeo, repartidores, categorías, planes) | `sui-select` | según la guía del DS (elegir una opción de un menú) |
45
+ | Más de ~6 opciones fijas | `sui-select` | canon (README de `sui-radio`, aprobado 03-10-2026) |
46
+ | 2 a 6 opciones fijas en un formulario | `sui-radio-label` / `sui-radio-card` | propuesta, falta aprobación (sale del umbral de `sui-radio`) |
47
+ | 2 a 4 modos que se aplican al instante | `sui-segmented-control` | canon (README de `sui-segmented-control`, aprobado 03-10-2026) |
48
+ | Filtro de una barra con 5 o más opciones | `sui-select` sin label (o `sui-compact-select` en web) | propuesta, falta aprobación |
49
+
50
+ **Candidatos de hoy** (no rompen nada; se verían mejor con radios o segmented):
51
+
52
+ - **6 selects con 2 opciones fijas**: `components/add-product-modal/add-product-modal.html:246`,
53
+ `pages/drivers/drivers.html:373` y `pages/drivers/add-driver/add-driver.html:111` («Tipo de pago»),
54
+ `pages/super-admin/master-gastos/master-gastos.html:185` y `:200`,
55
+ `pages/super-admin/master-sonidos-push/master-sonidos-push.html:85`.
56
+ - **15 con 3 a 6 opciones fijas en un formulario**: `components/address-form-modal/address-form-modal.html:11`
57
+ («Etiqueta»), `pages/customer/profile/profile.html:249` («Género»), `pages/drivers/drivers.html:359`
58
+ («Tipo de vehículo»), `pages/discounts/discounts.html:139`.
59
+ - **2 filtros de período de 3 opciones** → segmented: `pages/overview/overview.html:46`,
60
+ `pages/discounts/discounts.html:48`.
61
+
62
+ ### `type`, `leadingIcon`, `imageUrl`, `flag`, `badge`
63
+
64
+ | prop | cuándo | fuente |
65
+ |---|---|---|
66
+ | `type="basic"` (default) sin ícono | Todo select de la app hoy. | según la guía del DS |
67
+ | `type="avatar"` + `imageUrl` | Elegir un **repartidor**: la foto de quien quedó elegido en el campo. Hoy `pages/orders/orders.html:256` muestra solo el nombre. | según la guía del DS (ejemplo «User»); el caso es propuesta |
68
+ | `type="provider"` + `imageUrl` | Método de pago con logo (Yape, Plin). | según la guía del DS (ejemplo «Payment Method»); el caso es propuesta |
69
+ | `leadingIcon` (`true` o nombre RemixIcon) | Un ícono que ayuda a reconocer el campo en un formulario largo. Hoy sin caso. | según la guía del DS (ejemplo «Icons») |
70
+ | `type="country"` + `flag` | No usar hoy: todo es Perú. | según la guía del DS (ejemplo «Country») |
71
+ | `type="brand"` / `"company"` | No usar hoy. | — |
72
+ | `badge` | No usar hoy. | — |
73
+
74
+ ### Las piezas
75
+
76
+ | prop | cuándo | fuente |
77
+ |---|---|---|
78
+ | `label` | Siempre en formularios. Sin label solo en un filtro de barra donde el valor se explica solo («Todos los roles»). | según la guía del DS (ejemplo con label y hint) |
79
+ | `placeholder` | **Siempre**, en imperativo con tuteo: «Elige el motivo», «Elige tu distrito». El default es «Seleccionar...». | canon (CANON-SUI §5.3) |
80
+ | `required` · `sublabel` · `tooltip` · `hint` | Igual que `sui-input`: asterisco, «(Opcional)» con paréntesis, aclaración, ayuda de una línea. Hoy 1 sin paréntesis: `pages/customer/profile/profile.html:249`. | según la guía del DS |
81
+ | `error` + `errorMessage` | No eligió en un campo obligatorio: «Elige tu distrito». | según la guía del DS |
82
+ | `disabled` | Selects en cascada: provincia hasta elegir departamento, distrito hasta elegir provincia. | según la guía del DS (ejemplo deshabilitado); el caso ya es práctica (`components/address-form-modal/address-form-modal.html:87`) |
83
+ | `options` | `SuiSelectOption<T>[]`: `{ value, label, disabled? }`. `disabled` en la opción para lo que existe pero hoy no se puede elegir. | API |
84
+
85
+ **Usos de hoy que rompen la regla:**
86
+
87
+ - **28 sin `placeholder`** muestran «Seleccionar...» si arrancan vacíos.
88
+ - **16 placeholders «Seleccionar…» o «Selecciona…»** (y 4 que ya dicen «Elige…») → «Elige…»: «Seleccionar departamento»
89
+ (`components/address-form-modal/address-form-modal.html:77`), «Seleccionar tipo»,
90
+ «Selecciona una categoría».
91
+
92
+ ## Tamaño en mobile y en web
93
+
94
+ El campo sigue la escala del input y del botón (`large` 48 y `xlarge` 52 son extensión de SUI;
95
+ la guía tiene `medium` 40, `small` 36 y `xsmall` 32). **La lista acompaña al campo**: las opciones
96
+ miden 36 hasta `medium` (las de la guía), 44 en `large` y 48 en `xlarge`, y **en mobile (≤ 767 px)
97
+ nunca bajan de 44**, sea cual sea el `size`. El texto de la opción es el mismo del valor del
98
+ campo: 14/20 hasta `medium` y 16/24 en `large` y `xlarge`.
99
+
100
+ En large y xlarge el texto sube a 16 px (regla del 03-10-2026). Vale para el valor, el placeholder
101
+ y las opciones de la lista; el alto no cambia (las opciones bajan 2 de padding para seguir en 44/48).
102
+
103
+ | size del campo | opción en web | opción en mobile | fuente |
104
+ |---|---|---|---|
105
+ | `xsmall` · `small` · `medium` | 36 | 44 | 36: según la guía del DS · 44 en mobile: canon (aprobado por el dueño, 03-10-2026), extensión SUI |
106
+ | `large` | 44 | 44 | canon (aprobado por el dueño, 03-10-2026), extensión SUI |
107
+ | `xlarge` | 48 | 48 | canon (aprobado por el dueño, 03-10-2026), extensión SUI |
108
+
109
+ | uso | mobile | web | fuente |
110
+ |---|---|---|---|
111
+ | campo de formulario | `large` (48) | `medium` (40) | propuesta, falta aprobación (igual que `sui-input`) |
112
+ | filtro de una barra | `large` (48) | `small` (36) | propuesta (ya es práctica en `pages/orders/orders.html:256`) |
113
+ | dentro de una tabla densa | no aplica | `xsmall` (32) | según la guía del DS |
114
+ | `xlarge` (52) | no usar | no usar | propuesta: queda más alto que el botón `large` del footer |
115
+
116
+ ```html
117
+ <sui-select [size]="bp.isMobile() ? 'large' : 'medium'" label="Distrito" … /> <!-- bp = BreakpointsService -->
118
+ ```
119
+
120
+ ## «No usar»: por qué no hoy y cuándo sí
121
+
122
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
123
+ |---|---|---|
124
+ | `type="country"` + `flag` | todo es Perú | si se abren repartidores o locales en otro país |
125
+ | `type="brand"` / `"company"` | no hay listas de marcas o empresas | elegir la marca de un producto envasado o la empresa de una flota |
126
+ | `leadingIcon` | ningún formulario lo necesita para reconocerse | un formulario largo del panel con varios selects seguidos |
127
+ | `badge` | no hay valores que marcar | marcar el plan elegido como «Recomendado» |
128
+ | `size="xsmall"` | no hay selects dentro de tablas | cambiar el estado de una fila sin abrir el detalle |
129
+ | `size="xlarge"` | queda más alto que el botón del footer | un select solo, como acción principal de la pantalla |
130
+
131
+ ## API
132
+
133
+ <!-- api:inicio -->
134
+ <!-- generada: no editar a mano — `npm run docs` -->
135
+
136
+ | input | tipo | default |
137
+ |---|---|---|
138
+ | `size` | `'xsmall' \| 'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` |
139
+ | `placeholder` | `string` | `'Seleccionar...'` |
140
+ | `options` | `SuiSelectOption<T>[]` | `[]` |
141
+ | `disabled` | `boolean` | `false` |
142
+ | `error` | `boolean` | `false` |
143
+ | `errorMessage` | `string \| null` | `null` |
144
+ | `label` | `string \| null` | `null` |
145
+ | `hint` | `string \| null` | `null` |
146
+ | `type` | `'basic' \| 'country' \| 'avatar' \| 'provider' \| 'brand' \| 'company'` | `'basic'` |
147
+ | `leadingIcon` | `string \| boolean` | `false` |
148
+ | `flag` | `SuiFlagName \| null` | `null` |
149
+ | `imageUrl` | `string \| null` | `null` |
150
+ | `badge` | `SuiBadgeConfig \| string \| null` | `null` |
151
+ | `sublabel` | `string \| null` | `null` |
152
+ | `required` | `boolean` | `false` |
153
+ | `tooltip` | `string \| null` | `null` |
154
+ | `value` · model, `[(value)]` | `T \| null` | `null` |
155
+
156
+ Outputs: ninguno.
157
+ <!-- api:fin -->
158
+
159
+ `value` es `model<T | null>`. Implementa `ControlValueAccessor`. Slot `[slot=leading]` para una
160
+ decoración propia del campo (gana sobre ícono, imagen y bandera).
161
+
162
+ ## Trampas
163
+
164
+ - **No busca y no es múltiple.** Con 40 opciones la lista se scrollea; con varias a elegir no sirve.
165
+ - **Las opciones solo llevan texto**: `SuiSelectOption` no tiene ícono, foto ni bandera. El
166
+ `type="avatar"` o `"country"` decora **el campo**, no la lista.
167
+ - **El default del placeholder es «Seleccionar...»**: genérico y con `...`. Pásalo siempre.
168
+ - El `valueChange` puede traer `null`: los usos de hoy hacen `$event ?? ''`.
169
+ - `T` arranca en `any`: tipa las opciones (`SuiSelectOption<string>[]`) para no arrastrar `any`
170
+ a la pantalla.
171
+ - Con 44 px por opción en mobile el panel muestra unas 4 opciones antes de scrollear (el alto
172
+ máximo del panel no cambió).
173
+ - El label también abre la lista al tocarlo.
174
+
175
+ ## Ejemplo
176
+
177
+ ```html
178
+ <sui-select label="Distrito" [required]="true" placeholder="Elige tu distrito"
179
+ [options]="distritos()" [disabled]="!provinciaId()"
180
+ [error]="intentado() && !distritoId()" errorMessage="Elige tu distrito"
181
+ [size]="bp.isMobile() ? 'large' : 'medium'"
182
+ [value]="distritoId()" (valueChange)="distritoId.set($event ?? '')" />
183
+ ```
@@ -0,0 +1,217 @@
1
+ ---
2
+ name: sui-sidebar
3
+ selector: sui-sidebar
4
+ resumen: Navegación principal de un rol — columna fija a la izquierda en web y cajón que entra desde la izquierda en mobile
5
+ alternativas: [la barra inferior nativa del layout (las 4-5 secciones más usadas en el teléfono), sui-topbar (navegación arriba, sin columna lateral), sui-tab-menu-vertical (secciones dentro de UNA pantalla, como ajustes), sui-dropdown-menu (acciones de una cuenta o un botón)]
6
+ ---
7
+
8
+ # sui-sidebar
9
+
10
+ Story: [Organisms/Sidebar](https://sui-design-system.web.app/?path=/docs/organisms-sidebar--docs)
11
+
12
+ ## Cuándo usarlo / cuándo no
13
+
14
+ **Úsalo** como **la** navegación de un layout de rol: una por layout, montada en el layout y no
15
+ en las páginas. En la app hay 4 y cubren los roles: empresa (`components/sidebar/sidebar.html:43`,
16
+ dentro del shell), repartidor (`layouts/driver/driver-layout.html:4`), super admin y central
17
+ (`layouts/master/master-layout.html:5`) y cliente (`layouts/customer/customer-layout.html:5`).
18
+
19
+ **No lo uses** para:
20
+
21
+ - Navegar entre secciones **de una sola pantalla** (las pestañas de Ajustes, los pasos de un
22
+ formulario): `sui-tab-menu-vertical` o `sui-tab-chips`.
23
+ - Un menú de acciones (editar, eliminar, cerrar sesión de un item): `sui-dropdown-menu`.
24
+ - Reemplazar la barra inferior en el teléfono. En mobile, las secciones de todos los días van en la
25
+ barra inferior (nativa en iOS) y el sidebar es el cajón de «Más».
26
+
27
+ Regla corta: **navegación del rol → sidebar · secciones de una pantalla → tab menu · acciones → dropdown.**
28
+
29
+ ## Qué variante usar
30
+
31
+ > **La guía del DS no tiene página del sidebar.** El componente sale del Figma de SUI (Sidebar
32
+ > [Navigation], node 3802:11759) y el cajón de mobile no existe en ese Figma: se portó del layout
33
+ > del super admin. Lo de abajo es **práctica de la app** (los 4 usos están homologados desde el
34
+ > 25-07-2026) o **propuesta, falta aprobación**; lo marcado «canon» sale de `docs/CANON-SUI.md`
35
+ > o de `master-pedidos/CLAUDE.md`.
36
+ >
37
+ > Uso medido el 03-10-2026 en `master-pedidos/src/app`: **4 sidebars**, sin contar vitrinas
38
+ > (`master-preview` tiene el quinto).
39
+
40
+ ### La composición (igual en los 4 layouts)
41
+
42
+ | pieza | cómo | fuente |
43
+ |---|---|---|
44
+ | `brand` | `{ name: 'Master Pedidos', subtitle: '<Rol>', logo: 'assets/app-icon.png', fallback: 'M' }`. En empresa, el nombre del local. | práctica de la app (4 de 4) |
45
+ | `showHeaderButton` + `headerButtonActive` + panel `[header-panel]` | El botón del header abre el **cambio de rol** (o de negocio/sucursal en empresa). El panel se proyecta con `<div header-panel>` y adentro van `sui-dropdown-item mode="option" size="large"`. El cliente lo muestra solo en mobile. | práctica de la app (4 de 4) |
46
+ | tres secciones con rótulo | `items` + `itemsLabel` (lo de todos los días), `secondaryItems` + `secondaryItemsLabel` (dinero o crecimiento), `supportingItems` + `supportingItemsLabel` (cuenta, administración: va anclado abajo). Rótulos en sentence case: «Operación», «Dinero», «Cuenta». | práctica de la app (4 de 4) |
47
+ | `activeRoute` | Siempre `[activeRoute]="currentUrl()"`, aunque los items tengan `route`: así el activo se resuelve por prefijo (`/cliente/pedidos/123` deja marcado «Pedidos»). | práctica de la app (4 de 4) |
48
+ | `profile` | Nombre + `avatarUrl` (nunca `photoURL`) + correo. | canon (CLAUDE.md, «Fotos de otra persona»: el campo correcto es `avatarUrl`) |
49
+ | cerrar sesión | Hoy **ningún** item usa `action: 'logout'`: el cierre real vive en el selector de rol y el «salir» del panel solo cambia de rol (`components/sidebar/sidebar.ts:367`). Los 4 layouts dejan `(logoutClicked)` conectado por si se agrega. | práctica de la app |
50
+ | `[(mobileOpen)]` + `closeOnItemClick` | Siempre los dos. Más `cerrarAlNavegar(...)` en el constructor del layout (ver Trampas). | canon (CANON-SUI §1.1) |
51
+ | `search` | `{ placeholder: 'Buscar...', shortcut: '⌘K' }` y el layout **filtra** los items con `searchValue` (el componente no filtra). | práctica de la app (4 de 4) |
52
+ | `indicatorColor` | Default `brand` (naranja). No se cambia. | canon del DS (accent = marca) |
53
+ | `collapsed` | Solo el cliente lo usa, con un item «colapsar», y **solo en web**: `[collapsed]="colapsado() && !bp.isMobile()"`. | práctica de la app (1 uso) |
54
+
55
+ ### El item (`SuiSidebarItem`)
56
+
57
+ | prop | cuándo | fuente |
58
+ |---|---|---|
59
+ | `icon` | Siempre, RemixIcon `-line`. Sin ícono, el modo colapsado queda vacío. | propuesta, falta aprobación |
60
+ | `route` | Navegación normal (usa `routerLink`). | práctica de la app |
61
+ | sin `route` | Acción, no navegación (abrir Configuración, colapsar): `action` o solo `value`, y se escucha `(itemClicked)`. | práctica de la app |
62
+ | `badge` | Un contador que pide acción: notificaciones sin leer, con tope «99+» (`components/sidebar/sidebar.ts:129`). Nunca un «NEW» decorativo. | práctica de la app (el «nunca NEW» es propuesta) |
63
+ | `variant: 'danger'` | Solo «Cerrar sesión» o salidas destructivas. | propuesta, falta aprobación |
64
+ | `shortcut`, `colorDot` | No usar (ver «No usar»). | propuesta, falta aprobación |
65
+
66
+ ## Tamaño en mobile y en web
67
+
68
+ El sidebar **no tiene `size`**. El ancho lo decide el viewport:
69
+
70
+ | estado | ancho | fuente |
71
+ |---|---|---|
72
+ | web (> 767 px) | 272 fijo, alto `100dvh` | Figma |
73
+ | web colapsado (`collapsed`) | 80, solo íconos; los rótulos siguen visibles y cada item tiene tooltip nativo con su label | Figma |
74
+ | mobile `drawer` (default) | `min(86vw, 360px)`, entra desde la izquierda con fondo oscuro | extensión SUI (no está en el Figma) |
75
+ | mobile `sheet` | `100vw - 40px`, se siente casi a pantalla completa | extensión SUI |
76
+
77
+ | uso | mobile | web | fuente |
78
+ |---|---|---|---|
79
+ | navegación del rol | cajón `drawer`, cerrado por defecto | columna fija de 272 | práctica de la app (4 de 4) |
80
+ | colapsar | nunca (`!bp.isMobile()`) | opcional, a pedido de la persona | práctica de la app |
81
+ | `sheet` | no usar (0 usos) | — | propuesta, falta aprobación |
82
+
83
+ **Qué pasa en mobile (≤ 767 px):** el sidebar sale del flujo y queda `position: fixed` fuera de la
84
+ pantalla. Lo abre la acción `toggle-sidebar` de la **barra inferior** («Más»), que el layout
85
+ traduce a `mobileOpen = true` (`layouts/shell/shell.ts:326`, `customer-layout.ts:322`). Se cierra
86
+ con el fondo, con Escape, al tocar un item (`closeOnItemClick`) y —por el helper del proyecto—
87
+ al terminar cualquier navegación. Cerrado queda `inert` y `aria-hidden`: el foco no se mete.
88
+ Respeta las safe areas de arriba, izquierda y abajo.
89
+
90
+ ## «No usar»: por qué no hoy y cuándo sí
91
+
92
+ | qué | por qué no hoy | cuándo sí, en un flujo nuevo |
93
+ |---|---|---|
94
+ | `indicatorColor="information"` | el activo es naranja de marca en toda la app | nunca en esta app (es el azul del Figma original) |
95
+ | `mobileMode="sheet"` | el `drawer` deja ver la página detrás y se entiende que es un cajón | un rol con muchísimos items donde 360 px quedan cortos |
96
+ | `shortcut` en items | no hay atajos de teclado por sección | si se agregan atajos tipo `⌘1` en el panel web |
97
+ | `colorDot` | no hay «favoritos» ni etiquetas de color | proyectos o locales favoritos fijados por la persona |
98
+ | `profileDropdown` | el perfil se abre desde «Cuenta» | si el perfil pasa a tener menú propio (cambiar cuenta, idioma) |
99
+ | feature card (`[feature-card]`) | no hay promo dentro de la navegación | invitar a mejorar el plan en el panel de empresa |
100
+ | `showBackdrop` en `false` | sin fondo, el cajón parece pegado a la página | nunca en mobile |
101
+
102
+ ## API
103
+
104
+ <!-- api:inicio -->
105
+ <!-- generada: no editar a mano — `npm run docs` -->
106
+
107
+ | input | tipo | default |
108
+ |---|---|---|
109
+ | `collapsed` | `boolean` | `false` |
110
+ | `indicatorColor` | `'information' \| 'brand'` | `'brand'` |
111
+ | `brand` | `SuiSidebarBrandConfig \| null` | `null` |
112
+ | `showHeaderButton` | `boolean` | `false` |
113
+ | `headerButtonActive` | `boolean` | `false` |
114
+ | `search` | `SuiSidebarSearchConfig \| boolean \| null` | `false` |
115
+ | `searchValue` · model, `[(searchValue)]` | `string` | `''` |
116
+ | `items` | `SuiSidebarItem[]` | `[]` |
117
+ | `secondaryItems` | `SuiSidebarItem[]` | `[]` |
118
+ | `supportingItems` | `SuiSidebarItem[]` | `[]` |
119
+ | `supportingAnchorBottom` | `boolean` | `true` |
120
+ | `itemsLabel` | `string \| null` | `null` |
121
+ | `secondaryItemsLabel` | `string \| null` | `null` |
122
+ | `supportingItemsLabel` | `string \| null` | `null` |
123
+ | `activeRoute` | `string \| null` | `null` |
124
+ | `profile` | `SuiSidebarProfile \| null` | `null` |
125
+ | `profileDropdown` | `boolean` | `false` |
126
+ | `mobileOpen` · model, `[(mobileOpen)]` | `boolean` | `false` |
127
+ | `closeOnItemClick` | `boolean` | `true` |
128
+ | `showBackdrop` | `boolean` | `true` |
129
+ | `mobileMode` | `'drawer' \| 'sheet'` | `'drawer'` |
130
+
131
+ | output | payload |
132
+ |---|---|
133
+ | `itemClicked` | `SuiSidebarItem` |
134
+ | `logoutClicked` | `void` |
135
+ | `backdropClicked` | `void` |
136
+ | `headerButtonClicked` | `void` |
137
+ | `profileClicked` | `void` |
138
+ | `searchSubmitted` | `string` |
139
+ <!-- api:fin -->
140
+
141
+ ```ts
142
+ interface SuiSidebarItem {
143
+ value: string; // lo emite (itemClicked)
144
+ label: string;
145
+ icon?: string; // RemixIcon sin 'ri-'
146
+ route?: string; // routerLink; sin route solo emite
147
+ external?: boolean;
148
+ variant?: 'default' | 'danger';
149
+ action?: 'logout' | string; // cualquier action anula el route
150
+ badge?: string | number;
151
+ shortcut?: string;
152
+ colorDot?: string; // reemplaza el ícono por un punto de color
153
+ tooltip?: string; // tooltip en colapsado; default = label
154
+ }
155
+ interface SuiSidebarBrandConfig { name: string; subtitle?: string; subtitleAsBadge?: boolean; logo?: string; fallback?: string; }
156
+ interface SuiSidebarSearchConfig { placeholder?: string; shortcut?: string; }
157
+ interface SuiSidebarProfile { name: string; avatarUrl?: string; initials?: string; status?: SuiAvatarStatus; email?: string; verified?: boolean; }
158
+ ```
159
+
160
+ Slots: `[header-panel]` (debajo del header: el switcher), `[feature-card]` (al fondo del contenido).
161
+
162
+ ## Trampas
163
+
164
+ - **El cajón no se cierra solo al navegar desde afuera.** Se cierra con el fondo, Escape y el toque
165
+ en un item; cualquier otra navegación (atrás, un push, `router.navigate`, una ruta cacheada que
166
+ vuelve) lo deja abierto tapando la página siguiente. Mientras el DS no traiga `closeOnNavigate`,
167
+ cada layout llama `cerrarAlNavegar(menuAbierto, switcherAbierto)`
168
+ (`core/services/cerrar-al-navegar.ts`). Canon: CANON-SUI §1.1.
169
+ - **El buscador no filtra nada.** Solo expone `[(searchValue)]` y `(searchSubmitted)`; el layout
170
+ filtra los items (`master-layout.ts:125`). Y su placeholder por defecto es **`'Search...'`, en
171
+ inglés**: pásale siempre `{ placeholder: 'Buscar...' }`. (En `sui-topbar` el default sí es «Buscar...».)
172
+ - **`collapsed` no mira el viewport.** Con `collapsed` en `true`, el cajón de mobile también sale
173
+ de 80 px. Condiciónalo: `[collapsed]="colapsado() && !bp.isMobile()"`.
174
+ - Un item con `route` queda activo por `routerLinkActive` **y** por `activeRoute`; un item sin
175
+ `route` solo por `activeRoute` (por `value` exacto). Si no pasas `activeRoute`, los items-acción
176
+ nunca se marcan.
177
+ - Cualquier `action` (no solo `'logout'`) anula el `route`: el item no navega.
178
+ - El `title` nativo del host se borra (sin tooltip gris); en colapsado el tooltip va en cada item.
179
+ - El cajón tiene `z-index: 300` sobre la barra inferior (200). Mientras está abierto el layout
180
+ esconde la barra (`customer-layout.ts:66`); si armas un layout nuevo, haz lo mismo.
181
+ - El comentario de cabecera del `.ts` todavía dice 240 px; el ancho real es 272 (`sui-sidebar.scss:50`).
182
+ - Los items ya traen el arreglo de iOS del `:active` pegado: no lo agregues.
183
+
184
+ ## Ejemplo
185
+
186
+ ```html
187
+ <sui-sidebar
188
+ [brand]="{ name: 'Master Pedidos', subtitle: 'Repartidor', logo: 'assets/app-icon.png', fallback: 'M' }"
189
+ [showHeaderButton]="true"
190
+ [headerButtonActive]="switcherOpen()"
191
+ (headerButtonClicked)="switcherOpen.set(!switcherOpen())"
192
+ [search]="{ placeholder: 'Buscar...', shortcut: '⌘K' }"
193
+ [(searchValue)]="busqueda"
194
+ itemsLabel="Operación" [items]="items()"
195
+ secondaryItemsLabel="Dinero" [secondaryItems]="itemsDinero()"
196
+ supportingItemsLabel="Cuenta" [supportingItems]="itemsCuenta()"
197
+ [activeRoute]="currentUrl()"
198
+ [profile]="perfil()"
199
+ [(mobileOpen)]="menuAbierto"
200
+ [closeOnItemClick]="true"
201
+ (logoutClicked)="logout()">
202
+ @if (switcherOpen()) {
203
+ <div header-panel role="listbox" aria-label="Cambiar de rol">
204
+ @for (rol of roles; track rol.value) {
205
+ <sui-dropdown-item [label]="rol.label" [sublabel]="rol.sublabel" mode="option" size="large"
206
+ (clicked)="irARol(rol)" />
207
+ }
208
+ </div>
209
+ }
210
+ </sui-sidebar>
211
+ ```
212
+
213
+ ```ts
214
+ constructor() {
215
+ cerrarAlNavegar(this.menuAbierto, this.switcherOpen); // mientras el DS no cierre solo
216
+ }
217
+ ```