@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.
- package/docs/INDEX.md +75 -80
- package/docs/sui-accordion/README.md +171 -0
- package/docs/sui-activity-feed/README.md +155 -0
- package/docs/sui-activity-filter/README.md +100 -0
- package/docs/sui-alert/README.md +13 -0
- package/docs/sui-badge/README.md +17 -0
- package/docs/sui-banner/README.md +190 -0
- package/docs/sui-breadcrumb-item/README.md +96 -0
- package/docs/sui-breadcrumbs/README.md +122 -0
- package/docs/sui-button/README.md +36 -0
- package/docs/sui-card/README.md +168 -0
- package/docs/sui-chart-legend/README.md +140 -0
- package/docs/sui-chart-legend-dot/README.md +118 -0
- package/docs/sui-checkbox-card/README.md +150 -0
- package/docs/sui-command-menu-footer/README.md +100 -0
- package/docs/sui-command-menu-item/README.md +140 -0
- package/docs/sui-command-menu-search/README.md +105 -0
- package/docs/sui-compact-select/README.md +130 -0
- package/docs/sui-compact-select-for-input/README.md +120 -0
- package/docs/sui-content-card/README.md +147 -0
- package/docs/sui-content-label/README.md +148 -0
- package/docs/sui-counter-input/README.md +148 -0
- package/docs/sui-date-selector/README.md +98 -0
- package/docs/sui-datepicker/README.md +197 -0
- package/docs/sui-day-cell/README.md +118 -0
- package/docs/sui-day-label/README.md +81 -0
- package/docs/sui-drawer-footer/README.md +127 -0
- package/docs/sui-drawer-header/README.md +140 -0
- package/docs/sui-dropdown-caption/README.md +92 -0
- package/docs/sui-dropdown-item/README.md +163 -0
- package/docs/sui-dropdown-menu/README.md +196 -0
- package/docs/sui-dropdown-search/README.md +113 -0
- package/docs/sui-empty-state/README.md +145 -0
- package/docs/sui-file-upload/README.md +154 -0
- package/docs/sui-file-upload-card/README.md +129 -0
- package/docs/sui-filter/README.md +181 -0
- package/docs/sui-href/README.md +85 -0
- package/docs/sui-image-upload/README.md +200 -0
- package/docs/sui-image-upload-profile/README.md +145 -0
- package/docs/sui-img-retry/README.md +107 -0
- package/docs/sui-inline-input/README.md +104 -0
- package/docs/sui-inline-select/README.md +101 -0
- package/docs/sui-input/README.md +247 -0
- package/docs/sui-integration-switch/README.md +142 -0
- package/docs/sui-modal/README.md +238 -0
- package/docs/sui-modal-footer/README.md +178 -0
- package/docs/sui-modal-header/README.md +115 -0
- package/docs/sui-modal-sheet/README.md +170 -0
- package/docs/sui-notification/README.md +179 -0
- package/docs/sui-notification-footer/README.md +98 -0
- package/docs/sui-notification-tab-menu/README.md +112 -0
- package/docs/sui-otp-input/README.md +116 -0
- package/docs/sui-page-header/README.md +123 -0
- package/docs/sui-pagination/README.md +146 -0
- package/docs/sui-password-strength/README.md +99 -0
- package/docs/sui-period-range/README.md +93 -0
- package/docs/sui-popover/README.md +178 -0
- package/docs/sui-product-card/README.md +173 -0
- package/docs/sui-radio-card/README.md +160 -0
- package/docs/sui-rating/README.md +7 -6
- package/docs/sui-rich-editor/README.md +114 -0
- package/docs/sui-segmented-control/README.md +3 -0
- package/docs/sui-select/README.md +183 -0
- package/docs/sui-sidebar/README.md +217 -0
- package/docs/sui-stepper/README.md +166 -0
- package/docs/sui-store-card/README.md +147 -0
- package/docs/sui-switch-card/README.md +149 -0
- package/docs/sui-switch-label/README.md +3 -0
- package/docs/sui-tab-chips/README.md +188 -0
- package/docs/sui-tab-menu-horizontal/README.md +142 -0
- package/docs/sui-tab-menu-vertical/README.md +136 -0
- package/docs/sui-table/README.md +264 -0
- package/docs/sui-tag-input/README.md +123 -0
- package/docs/sui-textarea/README.md +160 -0
- package/docs/sui-time-picker/README.md +240 -0
- package/docs/sui-toast/README.md +190 -0
- package/docs/sui-tooltip/README.md +201 -0
- package/docs/sui-topbar/README.md +153 -0
- package/docs/sui-topbar-button/README.md +95 -0
- package/docs/sui-topbar-item/README.md +97 -0
- package/docs/sui-topbar-user/README.md +83 -0
- package/fesm2022/ajuarezso-sui-design-system.mjs +291 -246
- package/fesm2022/ajuarezso-sui-design-system.mjs.map +1 -1
- package/package.json +1 -1
- 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
|
+
```
|