@ajuarezso/sui-design-system 0.21.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 +25 -30
- 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-button/README.md +19 -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 +4 -1
- package/docs/sui-compact-select-for-input/README.md +3 -0
- package/docs/sui-counter-input/README.md +3 -0
- package/docs/sui-datepicker/README.md +4 -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-filter/README.md +181 -0
- package/docs/sui-href/README.md +85 -0
- package/docs/sui-img-retry/README.md +107 -0
- package/docs/sui-input/README.md +3 -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-page-header/README.md +123 -0
- package/docs/sui-segmented-control/README.md +3 -0
- package/docs/sui-select/README.md +5 -2
- package/docs/sui-sidebar/README.md +217 -0
- package/docs/sui-switch-label/README.md +3 -0
- package/docs/sui-tab-chips/README.md +5 -2
- package/docs/sui-table/README.md +264 -0
- package/docs/sui-tag-input/README.md +3 -0
- package/docs/sui-textarea/README.md +3 -0
- package/docs/sui-time-picker/README.md +3 -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 +32 -29
- package/fesm2022/ajuarezso-sui-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ajuarezso-sui-design-system.d.ts +18 -16
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-modal-sheet
|
|
3
|
+
selector: sui-modal-sheet
|
|
4
|
+
resumen: Hoja que sube desde abajo, se arrastra entre alturas fijas y puede dejar usable lo de atrás (un mapa)
|
|
5
|
+
alternativas: [sui-modal (pide terminar algo antes de seguir, sin fondo usable), sui-popover (contenido chico anclado a un botón), sui-dropdown-menu (lista de acciones)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-modal-sheet
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Modal/Modal Sheet](https://sui-design-system.web.app/?path=/docs/organisms-modal-modal-sheet--docs)
|
|
11
|
+
|
|
12
|
+
## Cuándo usarlo / cuándo no
|
|
13
|
+
|
|
14
|
+
**Úsalo** cuando el contenido **convive con lo de atrás** o se mira a distintas alturas:
|
|
15
|
+
|
|
16
|
+
- El panel del seguimiento sobre un mapa (pedido, envío, viaje): a 25 % se ve el mapa, a 100 %
|
|
17
|
+
el detalle. El mapa sigue respondiendo.
|
|
18
|
+
- Una lista o un detalle que se abre desde abajo y se cierra deslizando (reseñas, movimientos
|
|
19
|
+
de la billetera, elegir la hora de entrega).
|
|
20
|
+
|
|
21
|
+
**No lo uses** para:
|
|
22
|
+
|
|
23
|
+
- Un formulario o una confirmación que hay que terminar: `sui-modal`. El sheet se cierra con un
|
|
24
|
+
deslizón y se pierde lo escrito.
|
|
25
|
+
- Desktop como superficie principal: en una pantalla de 1440 px un sheet a todo el ancho se lee
|
|
26
|
+
como un error (ver tamaño). Si en desktop hay sitio, un panel lateral sirve mejor.
|
|
27
|
+
|
|
28
|
+
## Qué variante usar
|
|
29
|
+
|
|
30
|
+
> **No está en la guía del DS: es extensión de SUI**, inspirada en el `ion-modal` en modo sheet
|
|
31
|
+
> de Ionic y en las hojas de Apple Maps. No hay Figma que la legisle. Lo marcado **canon** sale
|
|
32
|
+
> de `master-pedidos/CLAUDE.md`; lo demás es **propuesta** sacada del uso real.
|
|
33
|
+
>
|
|
34
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app`: 13 sheets, sin contar prototipos ni
|
|
35
|
+
> vitrinas (`master-preview`, `master-prototipos`, `mockup-sheets-*`, `dx/*`, `master-rutina`,
|
|
36
|
+
> `master-medico`, `dev-index`) ni las copias de `components/sui/`.
|
|
37
|
+
|
|
38
|
+
### Lo que sí dice la guía del DS
|
|
39
|
+
|
|
40
|
+
Nada: el sheet no está en la guía. Lo único que hereda del DS es el criterio del body (20 px) y
|
|
41
|
+
el radio Android (`--sui-radius-20`).
|
|
42
|
+
|
|
43
|
+
### Los dos patrones de la app
|
|
44
|
+
|
|
45
|
+
El sheet no tiene variantes: se configura con tres números. En la app salen **dos recetas**, y
|
|
46
|
+
conviene copiar una de las dos en vez de inventar otra.
|
|
47
|
+
|
|
48
|
+
| patrón | `breakpoints` | `initialBreakpoint` | `backdropBreakpoint` | cuándo | ejemplo real de la app | fuente |
|
|
49
|
+
|---|---|---|---|---|---|---|
|
|
50
|
+
| **Sobre un mapa** | `[0.25, 0.5, 0.7, 1]` (sin `0`) | `0.5` | `1` | el seguimiento: el mapa se usa a cualquier altura y la hoja no se puede cerrar | `pages/customer/order-tracking/order-tracking.html:130` · `pages/driver/driver-order-detail/driver-order-detail.html:60` | propuesta |
|
|
51
|
+
| **Hoja de lectura** | `[0, 0.92]` | `0.92` | `0` | una lista o detalle que se abre y se cierra deslizando, con el fondo apagado | `pages/customer/order-tracking/order-tracking.html:809` (reseñas) · `pages/customer/checkout/checkout.html:448` (hora) | propuesta |
|
|
52
|
+
|
|
53
|
+
Hoy: 4 «sobre un mapa» (más 2 variantes con alturas propias: `pages/envios/envios.html:578` y
|
|
54
|
+
`pages/customer/viaje/viaje.html:24`), 5 hojas de lectura en `0.92`–`0.96`, 2 en `[0, 1]` y 1 en
|
|
55
|
+
`[0, 0.6, 0.95]`.
|
|
56
|
+
|
|
57
|
+
| input | regla | fuente |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `breakpoints` | Con `0` en la lista, la hoja **se cierra deslizando**. Sin `0`, no se cierra con el dedo (solo por código o backdrop). | componente |
|
|
60
|
+
| `initialBreakpoint` | Tiene que estar en `breakpoints`. | componente |
|
|
61
|
+
| `backdropBreakpoint` | `0` = fondo apagado desde que abre. `1` = **nunca hay backdrop**: el mapa de atrás se toca siempre. | componente |
|
|
62
|
+
| tope en las hojas de lectura | `0.92`, no `1`: deja ver arriba un borde de la pantalla de atrás, que es lo que dice «esto es una hoja». | propuesta |
|
|
63
|
+
| `showHandle` | Déjalo en `true` siempre que la hoja se arrastre. Hoy nadie lo apaga. | propuesta |
|
|
64
|
+
| `fullBody` | Solo para contenido a sangre (una foto, un mapa dentro de la hoja). | canon (CLAUDE.md, 2026-07-21) |
|
|
65
|
+
|
|
66
|
+
**Usos de hoy a revisar:**
|
|
67
|
+
|
|
68
|
+
- `pages/customer/viaje/viaje.html:24` reabre la hoja en `(closed)` (`hojaAbierta.set(true)`)
|
|
69
|
+
para que no se pueda cerrar. Sin `0` en `breakpoints` y con `[closeOnBackdrop]="false"` ya no se
|
|
70
|
+
cierra; el reabrir sobra o tapa otro caso.
|
|
71
|
+
- 2 footers de modal metidos en hojas: van sin `slot` (no hay slot de footer). Está bien, pero el
|
|
72
|
+
botón scrollea con el contenido: `pages/customer/checkout/checkout.html:490`,
|
|
73
|
+
`pages/super-admin/master-sonidos-push/master-sonidos-push.html:146`.
|
|
74
|
+
|
|
75
|
+
## Tamaño en mobile y en web
|
|
76
|
+
|
|
77
|
+
El sheet **no tiene `size`**. Mide lo que mide su contenido, con tope en el breakpoint más alto
|
|
78
|
+
(y nunca más que la pantalla menos el safe-area de arriba y 16 px).
|
|
79
|
+
|
|
80
|
+
| | mobile | web desktop | fuente |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| ancho | toda la pantalla menos 8 px a cada lado | **igual: todo el ancho menos 8 px** | componente |
|
|
83
|
+
| esquinas | iOS 36 arriba / 54 abajo · Android 20 | igual | canon (CLAUDE.md, 2026-07-22) |
|
|
84
|
+
| padding del body | `0 20px 20px` | igual | canon (CLAUDE.md, 2026-07-21) |
|
|
85
|
+
| ancho en desktop | — | acotarlo a una columna centrada de 560 px | propuesta (hoy lo hace cada pantalla a mano) |
|
|
86
|
+
|
|
87
|
+
**El sheet no se acota en desktop.** La app lo arregla con CSS propio, cinco reglas en cuatro archivos:
|
|
88
|
+
`components/store-ratings-modal/store-ratings-modal.scss:81`,
|
|
89
|
+
`pages/driver/movimientos/movimientos-sheet.scss:24`,
|
|
90
|
+
`pages/driver/driver-order-detail/driver-order-detail.scss:1028` y
|
|
91
|
+
`pages/customer/order-tracking/order-tracking.scss:1175` y `:1238`, todos a 560 px. Propuesta:
|
|
92
|
+
que el DS traiga ese tope (`max-width` desde 768 px, centrado) y se borren esos parches.
|
|
93
|
+
|
|
94
|
+
## API
|
|
95
|
+
|
|
96
|
+
<!-- api:inicio -->
|
|
97
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
98
|
+
|
|
99
|
+
| input | tipo | default |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `open` · model, `[(open)]` | `boolean` | `false` |
|
|
102
|
+
| `breakpoints` | `number[]` | `[0, 1]` |
|
|
103
|
+
| `initialBreakpoint` | `number` | `1` |
|
|
104
|
+
| `backdropBreakpoint` | `number` | `0` |
|
|
105
|
+
| `showHandle` | `boolean` | `true` |
|
|
106
|
+
| `closeOnBackdrop` | `boolean` | `true` |
|
|
107
|
+
| `closeOnEscape` | `boolean` | `true` |
|
|
108
|
+
| `fullBody` | `boolean` | `false` |
|
|
109
|
+
|
|
110
|
+
| output | payload |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `closed` | `void` |
|
|
113
|
+
| `breakpointChange` | `number` |
|
|
114
|
+
<!-- api:fin -->
|
|
115
|
+
|
|
116
|
+
Métodos públicos: `setCurrentBreakpoint(bp, animated = true)` (avisa por consola y no hace nada
|
|
117
|
+
si `bp` no está en `breakpoints`) y `close()`.
|
|
118
|
+
|
|
119
|
+
Signals públicas: `currentBreakpoint` y `progress` (cambian al soltar, no durante el arrastre) y
|
|
120
|
+
`dragging`.
|
|
121
|
+
|
|
122
|
+
Slot: un solo `<ng-content>`, el body. No hay header ni footer.
|
|
123
|
+
|
|
124
|
+
## Trampas
|
|
125
|
+
|
|
126
|
+
- **El padding del body es fuente única** (`0 20px 20px`). No le pongas padding lateral ni abajo
|
|
127
|
+
a tus secciones ni lo pises con `::ng-deep .sui-sheet__body`: el margen queda doble (pasó en el
|
|
128
|
+
tracking del repartidor: ~36 px a los lados y ~68 px bajo el último botón).
|
|
129
|
+
- **Anidar radios**: el body ya cumple `exterior = interior + padding` (36 − 20 = 16, el radio de
|
|
130
|
+
`sui-card`). Una card adentro no necesita otro radio.
|
|
131
|
+
- **`breakpointChange` y `progress` no cambian durante el arrastre**, solo al soltar. Si algo
|
|
132
|
+
tiene que seguir al dedo (el padding del mapa), usa `breakpointChange` y acepta el salto, o lee
|
|
133
|
+
la posición del panel.
|
|
134
|
+
- **Arrastre y scroll**: en la altura máxima el body scrollea; desde arriba del todo, jalar hacia
|
|
135
|
+
abajo mueve la hoja. En alturas intermedias el body entero arrastra (`touch-action: none`): un
|
|
136
|
+
carrusel o un slider dentro puede pelear con el gesto. Los campos (`input`, `textarea`,
|
|
137
|
+
`select`) no arrancan arrastre; para otro control que necesita el dedo, ponle el atributo
|
|
138
|
+
`data-sui-no-sheet-drag` (o a un ancestro).
|
|
139
|
+
- **Sin `0` en `breakpoints` no se cierra deslizando.** Si quieres que se cierre y no lo hace, es
|
|
140
|
+
eso.
|
|
141
|
+
- **`closeOnBackdrop` solo actúa cuando hay backdrop** (altura actual por encima de
|
|
142
|
+
`backdropBreakpoint`). Con `backdropBreakpoint` en `1` nunca hay.
|
|
143
|
+
- **El transform es tuyo, el movimiento no**: el sheet se mueve con la propiedad `translate` del
|
|
144
|
+
panel interno, así que puedes escribir `left` / `right` / `transform` sobre `.sui-sheet` (por
|
|
145
|
+
ejemplo para centrarlo en desktop) sin romper el arrastre.
|
|
146
|
+
- Pide ocultar la barra inferior de la app por `SUI_NAVBAR_VISIBILITY`, igual que `sui-modal`.
|
|
147
|
+
|
|
148
|
+
## Ejemplo
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<!-- Sobre un mapa: no se cierra, el mapa se usa siempre -->
|
|
152
|
+
<sui-modal-sheet
|
|
153
|
+
[(open)]="hojaAbierta"
|
|
154
|
+
[breakpoints]="[0.25, 0.5, 0.7, 1]"
|
|
155
|
+
[initialBreakpoint]="0.5"
|
|
156
|
+
[backdropBreakpoint]="1"
|
|
157
|
+
(breakpointChange)="mapa.setSheetRatio($event)">
|
|
158
|
+
<h2>Tu pedido va en camino</h2>
|
|
159
|
+
…
|
|
160
|
+
</sui-modal-sheet>
|
|
161
|
+
|
|
162
|
+
<!-- Hoja de lectura: se cierra deslizando -->
|
|
163
|
+
<sui-modal-sheet
|
|
164
|
+
[(open)]="resenasAbiertas"
|
|
165
|
+
[breakpoints]="[0, 0.92]"
|
|
166
|
+
[initialBreakpoint]="0.92"
|
|
167
|
+
[backdropBreakpoint]="0">
|
|
168
|
+
<app-ratings-detail … />
|
|
169
|
+
</sui-modal-sheet>
|
|
170
|
+
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-page-header
|
|
3
|
+
selector: sui-page-header
|
|
4
|
+
resumen: Título y subtítulo de una pantalla, con las acciones de la pantalla a la derecha en desktop y debajo en mobile
|
|
5
|
+
alternativas: [sui-modal-header (cabecera de un modal), sui-drawer-header (cabecera de un panel lateral), sui-topbar (barra superior de la app, no de la pantalla)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-page-header
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Page Header](https://sui-design-system.web.app/?path=/docs/organisms-page-header--docs)
|
|
11
|
+
|
|
12
|
+
## Cuándo usarlo / cuándo no
|
|
13
|
+
|
|
14
|
+
**Úsalo** una vez por pantalla, **arriba del todo**, antes de cualquier contenido: dice dónde
|
|
15
|
+
está la persona («Pedidos», «Descuentos») y, si hace falta, junta las acciones que valen para
|
|
16
|
+
toda la pantalla («Agregar», «Exportar»).
|
|
17
|
+
|
|
18
|
+
**No lo uses** para:
|
|
19
|
+
|
|
20
|
+
- Títulos de sección dentro de la pantalla: un `<h2>` con el token de tipografía, o el título de
|
|
21
|
+
la `sui-card`.
|
|
22
|
+
- Modales y paneles: `sui-modal-header` / `sui-drawer-header`.
|
|
23
|
+
- Meter contenido debajo del título: **solo** acepta `slot="actions"`; todo lo demás se descarta
|
|
24
|
+
sin avisar.
|
|
25
|
+
- Re-implementarlo con un `<h1>` y CSS propio (canon, CANON-SUI §0: «no re-implementar»).
|
|
26
|
+
|
|
27
|
+
## Qué variante usar
|
|
28
|
+
|
|
29
|
+
> **No está en la guía del DS: es extensión de SUI.** Nació como el título de las pantallas del
|
|
30
|
+
> panel de empresa y no tiene Figma. Lo marcado **canon** sale de `master-pedidos/docs/CANON-SUI.md`;
|
|
31
|
+
> lo demás es **propuesta**.
|
|
32
|
+
>
|
|
33
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app`: 64 page headers, sin contar prototipos ni
|
|
34
|
+
> vitrinas (`master-preview`, `master-prototipos`, `mockup-sheets-*`, `dx/*`, `master-rutina`,
|
|
35
|
+
> `master-medico`, `dev-index`) ni las copias de `components/sui/`. 50 llevan `subtitle` fijo, 14
|
|
36
|
+
> calculado; 17 tienen acciones; 0 usan `sticky`.
|
|
37
|
+
|
|
38
|
+
### Lo que sí dice la guía del DS
|
|
39
|
+
|
|
40
|
+
Nada: el page header no está en la guía. Su tipografía (24/600, 28 en desktop) **no existe en la
|
|
41
|
+
escala del DS** (CANON-SUI §7): si se homologa, sería `title-h5`.
|
|
42
|
+
|
|
43
|
+
### Contenido
|
|
44
|
+
|
|
45
|
+
| pieza | regla | ejemplo real de la app | fuente |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `title` | El nombre de la pantalla, el mismo que en el menú. Sentence case, sin punto. Nunca un ID. | `pages/discounts/discounts.html:2` | canon (CANON-SUI, copy) |
|
|
48
|
+
| `subtitle` | Una línea que dice **qué se hace aquí**, no que repite el título. | `pages/super-admin/master-orders/master-orders.html:1` | propuesta |
|
|
49
|
+
| acciones | Las que valen para toda la pantalla: una `filled` como máximo (la principal) y el resto `stroke`. Hoy: 9 `filled`, 7 `stroke`. | `pages/drivers/drivers.html:2` | canon para «una principal» (CANON-SUI §6.2), propuesta el resto |
|
|
50
|
+
|
|
51
|
+
**Usos de hoy que rompen el copy canon** (sentence case en botones): `pages/drivers/drivers.html:8`
|
|
52
|
+
(«Nuevo Repartidor»).
|
|
53
|
+
|
|
54
|
+
### `sticky`
|
|
55
|
+
|
|
56
|
+
| valor | cuándo | fuente |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `false` (default) | el caso normal: el título se va con el scroll | propuesta |
|
|
59
|
+
| `true` | listas largas donde las acciones del header se usan mientras se recorre (filtros, «Agregar»). Le pone fondo blanco. 0 usos hoy | propuesta |
|
|
60
|
+
|
|
61
|
+
## Tamaño en mobile y en web
|
|
62
|
+
|
|
63
|
+
El page header **no tiene `size`**. Cambia solo en 767 px:
|
|
64
|
+
|
|
65
|
+
| | mobile (< 767 px) | web (≥ 767 px) | fuente |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| título | 24 px | 28 px | componente |
|
|
68
|
+
| disposición | título y subtítulo arriba, acciones **debajo** | título a la izquierda, acciones a la derecha, alineadas abajo | componente |
|
|
69
|
+
| padding vertical | 24 arriba y abajo | 32 arriba, 16 abajo | componente |
|
|
70
|
+
| padding lateral | ninguno: lo pone el contenedor del layout | igual | componente |
|
|
71
|
+
| botones de las acciones | `large` (48) | `small` (36) | propuesta |
|
|
72
|
+
|
|
73
|
+
Los botones de las acciones hoy: 10 `small` fijos, 2 `bp.isMobile() ? 'large' : 'small'`, 2
|
|
74
|
+
`bp.controlSize()` (`large`/`xlarge`), 2 `large`, 1 `medium`. La propuesta es la pareja que ya
|
|
75
|
+
usan dos pantallas: en mobile las acciones bajan a su fila y tienen sitio para 48 px; en desktop
|
|
76
|
+
van al lado del título de 28 y `small` no le compite.
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<sui-button [size]="bp.isMobile() ? 'large' : 'small'" slot="actions">Agregar</sui-button>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API
|
|
83
|
+
|
|
84
|
+
<!-- api:inicio -->
|
|
85
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
86
|
+
|
|
87
|
+
| input | tipo | default |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `title` | `string` | — (requerido) |
|
|
90
|
+
| `subtitle` | `string \| null` | `null` |
|
|
91
|
+
| `sticky` | `boolean` | `false` |
|
|
92
|
+
|
|
93
|
+
Outputs: ninguno.
|
|
94
|
+
<!-- api:fin -->
|
|
95
|
+
|
|
96
|
+
Slot: `slot="actions"`, a la derecha (desktop) o debajo (mobile), gap 8. No hay slot default.
|
|
97
|
+
|
|
98
|
+
## Trampas
|
|
99
|
+
|
|
100
|
+
- **Lo que proyectas sin `slot="actions"` desaparece.** El JSDoc viejo hablaba de un slot
|
|
101
|
+
default; no existe.
|
|
102
|
+
- **No respeta el safe-area de arriba**: el padding es fijo. El layout de la pantalla se encarga
|
|
103
|
+
del notch; si lo pones en una pantalla sin layout, el título queda bajo la barra de estado.
|
|
104
|
+
- **Sin padding lateral**: depende de que el contenedor de la página lo tenga
|
|
105
|
+
(`.layout__body`, `.ml__content`). Fuera de esos layouts queda pegado al borde.
|
|
106
|
+
- `sticky` se pega a `top: 0` del **contenedor que scrollea**, no de la ventana. En los layouts de
|
|
107
|
+
la app el scroll vive en `.layout__body` / `.ml__content`, así que funciona; en un contenedor
|
|
108
|
+
sin overflow no hace nada.
|
|
109
|
+
- El breakpoint es `min-width: 767px`: a 767 px exactos ya está en modo desktop, un píxel antes
|
|
110
|
+
que el `max-width: 767px` de los mixins de la app.
|
|
111
|
+
- `title` es requerido: sin él, Angular falla al compilar la plantilla.
|
|
112
|
+
- El host borra el atributo `title` nativo (sin tooltip gris del navegador).
|
|
113
|
+
|
|
114
|
+
## Ejemplo
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<sui-page-header title="Descuentos" subtitle="Promociones y campañas activas de tu negocio.">
|
|
118
|
+
<sui-button slot="actions" variant="filled"
|
|
119
|
+
[size]="bp.isMobile() ? 'large' : 'small'" (click)="crear()">
|
|
120
|
+
Crear descuento
|
|
121
|
+
</sui-button>
|
|
122
|
+
</sui-page-header>
|
|
123
|
+
```
|
|
@@ -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` |
|
|
@@ -94,8 +94,11 @@ Regla corta: **sale de datos o son más de 6 → select · 2–6 fijas → radio
|
|
|
94
94
|
El campo sigue la escala del input y del botón (`large` 48 y `xlarge` 52 son extensión de SUI;
|
|
95
95
|
la guía tiene `medium` 40, `small` 36 y `xsmall` 32). **La lista acompaña al campo**: las opciones
|
|
96
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
|
|
98
|
-
|
|
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).
|
|
99
102
|
|
|
100
103
|
| size del campo | opción en web | opción en mobile | fuente |
|
|
101
104
|
|---|---|---|---|
|
|
@@ -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
|
+
```
|
|
@@ -80,6 +80,9 @@ Regla corta: **ajuste con texto → este · ajuste con peso → switch-card · s
|
|
|
80
80
|
El bloque entero se toca, así que con descripción la fila pasa los 44 px sola. Sin descripción,
|
|
81
81
|
dale a la fila un alto mínimo de 44 px.
|
|
82
82
|
|
|
83
|
+
En large y xlarge el texto sube a 16 px (regla del 03-10-2026): el label va en 16/24; sublabel y
|
|
84
|
+
descripción se quedan en 12.
|
|
85
|
+
|
|
83
86
|
## «No usar»: por qué no hoy y cuándo sí
|
|
84
87
|
|
|
85
88
|
| qué | por qué no hoy | cuándo sí, en un flujo nuevo |
|
|
@@ -108,8 +108,11 @@ vista → segmented · secciones → tab menu · filtros o categorías → tab-c
|
|
|
108
108
|
> `size` está **en el repo del DS (03-10-2026) y no en la versión publicada 0.19.0**. Hasta que se
|
|
109
109
|
> publique, todos miden lo de `medium`.
|
|
110
110
|
|
|
111
|
-
Alto del chip: `small` 32 px · `medium` 36 px (el de siempre, default) · `large` 40 px.
|
|
112
|
-
con texto de 14 px; cambia el alto y el aire lateral. El scroller suma 12 px arriba y abajo.
|
|
111
|
+
Alto del chip: `small` 32 px · `medium` 36 px (el de siempre, default) · `large` 40 px. `small` y
|
|
112
|
+
`medium` con texto de 14 px; cambia el alto y el aire lateral. El scroller suma 12 px arriba y abajo.
|
|
113
|
+
|
|
114
|
+
En large y xlarge el texto sube a 16 px (regla del 03-10-2026): el chip `large` va en 16/24 y sigue
|
|
115
|
+
midiendo 40. El número del contador se queda en 12.
|
|
113
116
|
|
|
114
117
|
| uso | mobile | web | fuente |
|
|
115
118
|
|---|---|---|---|
|