@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,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-dropdown-menu
|
|
3
|
+
selector: sui-dropdown-menu, ng-template[suiDropdownPanel]
|
|
4
|
+
resumen: Menú flotante anclado a un botón (el kebab ⋮ de una card, un avatar) con acciones sobre eso; también es el overlay que usan por dentro los selects
|
|
5
|
+
alternativas: [sui-select (elegir un valor de un formulario), sui-modal-sheet (más de 5 acciones o una lista larga en mobile), sui-popover (explicar algo con una flecha, sin anclaje propio), sui-tooltip (ayuda corta al pasar el mouse), sui-command-menu-item (buscar y saltar con ⌘K en desktop)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-dropdown-menu
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Dropdown/Dropdown Menu](https://sui-design-system.web.app/?path=/docs/organisms-dropdown-dropdown-menu--docs)
|
|
11
|
+
|
|
12
|
+
Es **solo el contenedor**: abre y cierra el panel, lo ancla al trigger (CDK Overlay), lo cierra al
|
|
13
|
+
tocar afuera o con Escape y lo hace seguir al scroll. Lo que va adentro se arma con las piezas
|
|
14
|
+
hermanas: `sui-dropdown-item` (las filas), `sui-dropdown-search` (el buscador de arriba) y
|
|
15
|
+
`sui-dropdown-caption` (el texto de pie).
|
|
16
|
+
|
|
17
|
+
## Cuándo usarlo / cuándo no
|
|
18
|
+
|
|
19
|
+
**Úsalo** para un puñado de **acciones sobre algo concreto** que no merecen estar siempre a la
|
|
20
|
+
vista: el ⋮ de una card («Editar», «Aplicar a productos», «Archivar»), el menú de una fila, un
|
|
21
|
+
cambio de cuenta. También para anclar un panel chico a un botón cuando `sui-popover` no sirve
|
|
22
|
+
(no se ancla solo).
|
|
23
|
+
|
|
24
|
+
**No lo uses** para:
|
|
25
|
+
|
|
26
|
+
- Elegir un valor de un formulario (distrito, categoría): `sui-select`, que ya es un
|
|
27
|
+
`sui-dropdown-menu` por dentro con su campo, su teclado y su `ControlValueAccessor`. Armar un
|
|
28
|
+
select a mano con este componente es rehacer lo que el select ya resuelve.
|
|
29
|
+
- Una sola acción: un botón con su ícono (lápiz, tacho). Un ⋮ que abre un menú de un solo item es
|
|
30
|
+
un toque de más.
|
|
31
|
+
- Acciones que la persona usa todo el rato: van a la vista, como botones.
|
|
32
|
+
- Buscar en toda la app: el command menu (`sui-command-menu-search` + `-item` + `-footer`).
|
|
33
|
+
- Explicar algo: `sui-tooltip` (web) o `sui-popover`.
|
|
34
|
+
|
|
35
|
+
Regla corta: **acciones sobre algo → dropdown-menu · elegir un valor → select · buscar y saltar →
|
|
36
|
+
command menu · explicar → tooltip/popover.**
|
|
37
|
+
|
|
38
|
+
## Dropdown, select y command menu: cuál va
|
|
39
|
+
|
|
40
|
+
Los tres muestran una lista flotante, pero responden a preguntas distintas.
|
|
41
|
+
|
|
42
|
+
| pregunta de la persona | componente | items | rol ARIA | fuente |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| «¿qué hago con esto?» (editar, archivar, compartir) | `sui-dropdown-menu` | `sui-dropdown-item` (default `mode="menuitem"`) | `menu` (default) | según la guía del DS («un menú con un conjunto de acciones, abierto desde un botón») |
|
|
45
|
+
| «¿cuál de estos valores elijo?» | `sui-select` / `sui-compact-select` | los arma el select: `sui-dropdown-item mode="option"` | `listbox` | según la guía del DS (el select es el control de una opción) |
|
|
46
|
+
| «¿dónde está X?» (buscar y saltar a cualquier lado) | command menu: `sui-command-menu-search` + `sui-command-menu-item` + `sui-command-menu-footer` dentro de un `sui-modal` | `sui-command-menu-item` | lo arma quien lo usa | según la guía del DS (el command menu vive dentro de un diálogo) |
|
|
47
|
+
|
|
48
|
+
Por dentro, `sui-select`, `sui-compact-select`, `sui-compact-select-for-input`, `sui-inline-select`,
|
|
49
|
+
`sui-time-picker` y `sui-color-picker` usan este mismo componente con `[origin]` (el trigger es su
|
|
50
|
+
propio campo, que ya lleva su `aria-haspopup`: `listbox` en los selects, `dialog` en los pickers). Por
|
|
51
|
+
eso un arreglo del overlay los toca a todos. Con `[origin]`, `popupRole` no se usa: el wrapper del
|
|
52
|
+
trigger no se dibuja.
|
|
53
|
+
|
|
54
|
+
## Qué variante usar
|
|
55
|
+
|
|
56
|
+
> **La guía del DS lo define como el menú de acciones que abre un botón** y lo arma por partes:
|
|
57
|
+
> item con ícono, grupo con su etiqueta, separador, items con casilla o radio y submenús. No trae
|
|
58
|
+
> variantes ni do/don't; el panel arranca en 300 px de ancho y 8 px separado del trigger. Lo
|
|
59
|
+
> demás es **propuesta, falta aprobación**, salvo lo marcado canon.
|
|
60
|
+
>
|
|
61
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app` (sin `master-preview`, `master-prototipos`,
|
|
62
|
+
> `mockup-sheets-*`, `dx/*`, `master-rutina`, `master-medico`, `master-ejercicios`, `dev-index` ni las
|
|
63
|
+
> copias de `components/sui/`): **2 usos**. `components/option-sections-manager/option-sections-manager.html:37`
|
|
64
|
+
> (el ⋮ de una sección de opciones, con 3 acciones) y `components/option-image/option-image.ts:52`
|
|
65
|
+
> (panel con un campo para pegar un link, `popupRole="true"`).
|
|
66
|
+
|
|
67
|
+
### Lo que sí dice la guía del DS
|
|
68
|
+
|
|
69
|
+
- **Para qué es**: mostrar un menú de acciones o funciones, abierto desde un botón.
|
|
70
|
+
- **Se arma por partes**, no por tipos: el ícono, la bandera, el avatar, el badge o el atajo van
|
|
71
|
+
dentro del item. En SUI eso es el slot `leading` / `trailing` de `sui-dropdown-item`.
|
|
72
|
+
- **Grupos**: la guía tiene etiqueta de grupo y separador. SUI no trae una pieza propia para la
|
|
73
|
+
etiqueta: se usa `sui-content-divider` (`text`) para la cabecera y `sui-divider` para el separador.
|
|
74
|
+
- **Submenús, items con casilla y con radio**: existen en la guía; SUI no los tiene armados.
|
|
75
|
+
|
|
76
|
+
### Las props que importan
|
|
77
|
+
|
|
78
|
+
| prop | cuándo | fuente |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| sin props (default) | El caso normal: un ⋮ o un botón con 2 a 5 acciones. El panel se ajusta al contenido. | propuesta |
|
|
81
|
+
| `matchWidth` | El panel debe medir lo mismo que el trigger (un botón ancho que abre opciones). | propuesta |
|
|
82
|
+
| `minWidth` | El trigger es un ícono chico y el panel quedaría angosto: 200 a 240 px. | propuesta |
|
|
83
|
+
| `position="above"` | El trigger vive pegado al borde de abajo (una barra inferior). Con `auto` ya cae arriba si no entra. | propuesta |
|
|
84
|
+
| `popupRole="listbox"` | **Solo** si los items son `mode="option"`. El rol tiene que coincidir con el de los items. | según ARIA (documentado en el `.ts`) |
|
|
85
|
+
| `popupRole="true"` | El panel no es un menú: un campo, un formulario chico (`option-image.ts:52`). | propuesta (ya es práctica) |
|
|
86
|
+
| `origin` | El trigger lo maneja el padre (los selects del DS). En la app no hace falta. | propuesta |
|
|
87
|
+
| `push` | No usar: un panel empujado queda fijo y **se despega del trigger al scrollear**. | canon (comentario del `.ts`, bug real del color picker 21-07-2026) |
|
|
88
|
+
| `flexibleDimensions=false` | Solo paneles de tamaño fijo que no pueden encogerse (el color picker). | propuesta |
|
|
89
|
+
|
|
90
|
+
**Usos de hoy que rompen la propuesta:**
|
|
91
|
+
|
|
92
|
+
- **Un ⋮ que no abre menú**: `pages/products/products.html:229` pinta `more-2-fill` («Opciones») y al
|
|
93
|
+
tocarlo abre la edición directo. O se cambia el ícono por el lápiz, o se abre un menú con las
|
|
94
|
+
acciones del producto.
|
|
95
|
+
- **Cabeceras de grupo a mano** en el switcher del sidebar: `components/sidebar/sidebar.html:67`,
|
|
96
|
+
`:88` y `:104` usan `<span class="sidebar-switcher__label">` («Negocios», «Sucursales», «Cambiar
|
|
97
|
+
de rol») → `sui-content-divider` `text`.
|
|
98
|
+
|
|
99
|
+
## Tamaño en mobile y en web
|
|
100
|
+
|
|
101
|
+
El menú **no tiene `size`**: el alto lo ponen los items. El panel mide lo que su contenido, con
|
|
102
|
+
8 px de padding, 4 px entre items y hasta 400 px de alto (después scrollea).
|
|
103
|
+
|
|
104
|
+
| uso | mobile | web | fuente |
|
|
105
|
+
|---|---|---|---|
|
|
106
|
+
| items de un menú de acciones | `sui-dropdown-item size="large"` (56) | `size="small"` (36) | propuesta; hoy los 3 items del ⋮ van en `large` en las dos |
|
|
107
|
+
| trigger ⋮ | `sui-compact-button` de 40 o más | `sui-compact-button` `large` | propuesta (ver README de `sui-compact-button`) |
|
|
108
|
+
| más de 5 acciones, o alguna destructiva que pide confirmar | `sui-modal-sheet` | `sui-dropdown-menu` | propuesta, falta aprobación |
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<sui-dropdown-item [size]="bp.isMobile() ? 'large' : 'small'" … /> <!-- bp = BreakpointsService -->
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API
|
|
115
|
+
|
|
116
|
+
<!-- api:inicio -->
|
|
117
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
118
|
+
|
|
119
|
+
### `sui-dropdown-menu`
|
|
120
|
+
|
|
121
|
+
| input | tipo | default |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `matchWidth` | `boolean` | `false` |
|
|
124
|
+
| `flexibleDimensions` | `boolean` | `true` |
|
|
125
|
+
| `push` | `boolean` | `false` |
|
|
126
|
+
| `minWidth` | `number \| null` | `null` |
|
|
127
|
+
| `position` | `'auto' \| 'below' \| 'above'` | `'auto'` |
|
|
128
|
+
| `viewportMargin` | `number` | `8` |
|
|
129
|
+
| `panelClass` | `string` | `''` |
|
|
130
|
+
| `disabled` | `boolean` | `false` |
|
|
131
|
+
| `popupRole` | `'menu' \| 'listbox' \| 'true'` | `'menu'` |
|
|
132
|
+
| `origin` | `ElementRef<HTMLElement> \| HTMLElement \| null` | `null` |
|
|
133
|
+
|
|
134
|
+
| output | payload |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `openedChange` | `boolean` |
|
|
137
|
+
|
|
138
|
+
### `ng-template[suiDropdownPanel]`
|
|
139
|
+
|
|
140
|
+
Inputs: ninguno.
|
|
141
|
+
|
|
142
|
+
Outputs: ninguno.
|
|
143
|
+
<!-- api:fin -->
|
|
144
|
+
|
|
145
|
+
`exportAs: 'suiDropdownMenu'`. Métodos públicos: `open()`, `close()`, `toggle()`. Signal pública:
|
|
146
|
+
`isOpen` (`signal(false)`).
|
|
147
|
+
|
|
148
|
+
Slots: el trigger se proyecta con el atributo `trigger` (`<sui-compact-button trigger … />`); el
|
|
149
|
+
panel va en un `<ng-template suiDropdownPanel>` (hay que importar `SuiDropdownPanel` junto con
|
|
150
|
+
`SuiDropdownMenu`).
|
|
151
|
+
|
|
152
|
+
## Trampas
|
|
153
|
+
|
|
154
|
+
- **El paquete npm no envía estilos.** La caja del panel (fondo, borde, radio, sombra, padding,
|
|
155
|
+
`max-height`) vive en `src/styles/_sui-dropdown-menu-overlay.scss` del DS, y la app tiene que
|
|
156
|
+
importar su propia copia (`master-pedidos/src/styles.scss:13`). **La copia de la app está
|
|
157
|
+
desfasada**: usa `radius-16` + `regular-shadow-medium`; el DS ya va en `radius-10` + `x-small`
|
|
158
|
+
(alineado al Figma del dropdown).
|
|
159
|
+
- **El item no cierra el menú.** Hay que cerrarlo en el handler:
|
|
160
|
+
`#menu="suiDropdownMenu"` y `(clicked)="menu.close(); editar()"`.
|
|
161
|
+
- **El panel tiene que ir en `<ng-template suiDropdownPanel>`.** Si los items se ponen directo
|
|
162
|
+
dentro del `sui-dropdown-menu`, no se proyectan en ningún lado y el menú abre vacío (o no abre:
|
|
163
|
+
`open()` sale sin panel).
|
|
164
|
+
- **El trigger necesita el atributo `trigger`.** Sin él no se proyecta.
|
|
165
|
+
- **El primer toque afuera solo cierra**: el backdrop es transparente y se come ese toque; no le
|
|
166
|
+
llega al elemento de abajo.
|
|
167
|
+
- **No hay flechas entre items.** Enter, Espacio y Flecha abajo abren el menú desde el trigger,
|
|
168
|
+
pero adentro se navega con Tab. Y al abrir, el foco **se queda en el trigger**.
|
|
169
|
+
- `disabled` no abre el menú pero **no pinta nada**: deshabilita también el trigger.
|
|
170
|
+
- El lado (arriba o abajo) se decide al abrir y queda fijo mientras está abierto; si el trigger
|
|
171
|
+
está pegado al borde derecho, el panel abre hacia la izquierda.
|
|
172
|
+
- Se cierra solo al cambiar de ruta (`disposeOnNavigation`).
|
|
173
|
+
- **El `z-index` del overlay también es de esa hoja.** El DS sube `.cdk-overlay-container` a 1100
|
|
174
|
+
(encima de `sui-modal`, 1000/1001, y debajo de los toasts); con el 1000 del CDK, un menú abierto
|
|
175
|
+
dentro de un modal queda detrás. La copia de la app no trae ese bloque: no se verificó en la
|
|
176
|
+
app, pero es lo primero que hay que mirar si un menú «no abre» dentro de un modal.
|
|
177
|
+
- El host es `display: contents`: no ocupa caja. Para alinearlo, se alinea el trigger.
|
|
178
|
+
|
|
179
|
+
## Ejemplo
|
|
180
|
+
|
|
181
|
+
```html
|
|
182
|
+
<sui-dropdown-menu #menu="suiDropdownMenu">
|
|
183
|
+
<sui-compact-button trigger icon="more-2-line" variantStyle="ghost" size="large"
|
|
184
|
+
ariaLabel="Opciones de la sección" />
|
|
185
|
+
<ng-template suiDropdownPanel>
|
|
186
|
+
<sui-dropdown-item label="Editar" [size]="bp.isMobile() ? 'large' : 'small'"
|
|
187
|
+
(clicked)="menu.close(); editar()">
|
|
188
|
+
<sui-icon slot="leading" name="pencil-line" [size]="20" />
|
|
189
|
+
</sui-dropdown-item>
|
|
190
|
+
<sui-dropdown-item label="Archivar" [size]="bp.isMobile() ? 'large' : 'small'"
|
|
191
|
+
(clicked)="menu.close(); archivar()">
|
|
192
|
+
<sui-icon slot="leading" name="archive-line" [size]="20" />
|
|
193
|
+
</sui-dropdown-item>
|
|
194
|
+
</ng-template>
|
|
195
|
+
</sui-dropdown-menu>
|
|
196
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-dropdown-search
|
|
3
|
+
selector: sui-dropdown-search
|
|
4
|
+
resumen: Buscador sin caja que va arriba del panel de un menú flotante para filtrar sus items
|
|
5
|
+
alternativas: [sui-input type="search" (buscador de una pantalla o una lista a la vista), sui-command-menu-search (buscador del ⌘K), sui-select (elegir de una lista corta, sin buscar)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-dropdown-search
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Dropdown/Dropdown Search](https://sui-design-system.web.app/?path=/docs/organisms-dropdown-dropdown-search--docs)
|
|
11
|
+
|
|
12
|
+
Es solo el campo: **no filtra nada**. Quien lo usa recibe el texto (`[(value)]`) y decide qué items
|
|
13
|
+
pinta debajo.
|
|
14
|
+
|
|
15
|
+
## Cuándo usarlo / cuándo no
|
|
16
|
+
|
|
17
|
+
**Úsalo** arriba del panel de un `sui-dropdown-menu` cuando la lista es larga para scrollear y la
|
|
18
|
+
persona sabe lo que busca: más de ~10 opciones (un repartidor entre 40, un país, una etiqueta).
|
|
19
|
+
|
|
20
|
+
**No lo uses** para:
|
|
21
|
+
|
|
22
|
+
- El buscador de una pantalla o de una lista que ya está a la vista: `sui-input type="search"`
|
|
23
|
+
(con caja, label y tamaños).
|
|
24
|
+
- El ⌘K que busca en toda la app: `sui-command-menu-search`.
|
|
25
|
+
- Una lista corta: con 6 opciones el buscador estorba.
|
|
26
|
+
- Fuera de un panel: no tiene borde ni radio, se pierde en la pantalla.
|
|
27
|
+
|
|
28
|
+
Regla corta: **filtrar los items de un menú → dropdown-search · buscar en una pantalla → sui-input
|
|
29
|
+
search · buscar en toda la app → command-menu-search.**
|
|
30
|
+
|
|
31
|
+
`sui-select` **no tiene buscador**. Si un select necesita buscar, hoy se arma con
|
|
32
|
+
`sui-dropdown-menu` + este buscador + `sui-dropdown-item mode="option"` (es lo que muestra su story
|
|
33
|
+
«En contexto»), sabiendo que se pierde el `ControlValueAccessor` del select. Propuesta, falta
|
|
34
|
+
aprobación.
|
|
35
|
+
|
|
36
|
+
## Qué variante usar
|
|
37
|
+
|
|
38
|
+
> **La guía del DS no tiene buscador en su dropdown**; el buscador de lista vive en su command
|
|
39
|
+
> menu. Este sale del Figma del DS («Dropdown Misc Items», estados Default / Hover / Active). Todo lo
|
|
40
|
+
> de abajo es **propuesta, falta aprobación**, salvo lo marcado canon.
|
|
41
|
+
>
|
|
42
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-dropdown-menu`):
|
|
43
|
+
> **0 usos**.
|
|
44
|
+
|
|
45
|
+
No tiene variantes. Los estados salen solos: sin texto, lupa gris; con texto, lupa oscura y una X
|
|
46
|
+
para limpiar.
|
|
47
|
+
|
|
48
|
+
| prop | regla | fuente |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `placeholder` | **Siempre**, en español y con elipsis: «Buscar repartidor…». El default es `'Search...'`. | canon (CANON-SUI §5: copy en español, elipsis `…`) |
|
|
51
|
+
| `[(value)]` | El texto; con él se filtra la lista. | API |
|
|
52
|
+
| `disabled` | Extensión SUI, no está en el Figma. Sin caso hoy. | — |
|
|
53
|
+
|
|
54
|
+
## Tamaño en mobile y en web
|
|
55
|
+
|
|
56
|
+
No tiene tamaños: 36 px de alto (8 + 20 + 8), texto 14/20, en los dos.
|
|
57
|
+
|
|
58
|
+
| uso | mobile | web | fuente |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| buscador del menú | igual (36) | igual (36) | propuesta |
|
|
61
|
+
|
|
62
|
+
En mobile tiene dos problemas que el componente no resuelve (ver «Trampas»): el texto de 14 px hace
|
|
63
|
+
que iOS haga zoom al enfocarlo, y el teclado no muestra la lupa. Para buscar en una lista larga en
|
|
64
|
+
mobile, mejor un `sui-modal-sheet` con `sui-input type="search"` arriba. Propuesta, falta aprobación.
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
<!-- api:inicio -->
|
|
69
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
70
|
+
|
|
71
|
+
| input | tipo | default |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `value` · model, `[(value)]` | `string` | `''` |
|
|
74
|
+
| `placeholder` | `string` | `'Search...'` |
|
|
75
|
+
| `disabled` | `boolean` | `false` |
|
|
76
|
+
|
|
77
|
+
| output | payload |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `cleared` | `void` |
|
|
80
|
+
| `valueChanged` | `string` |
|
|
81
|
+
<!-- api:fin -->
|
|
82
|
+
|
|
83
|
+
`valueChanged` sale con cada tecla (lo mismo que `value`, como evento). `cleared` sale al tocar la X,
|
|
84
|
+
que además deja `value` en `''`.
|
|
85
|
+
|
|
86
|
+
## Trampas
|
|
87
|
+
|
|
88
|
+
- **No filtra.** Hay que filtrar la lista con el `value` (un `computed` sobre las opciones).
|
|
89
|
+
- **El default del placeholder es `'Search...'`**, en inglés y con tres puntos. Pásalo siempre.
|
|
90
|
+
- **iOS hace zoom al enfocarlo**: el campo está en 14 px y la regla de la app pide 16 px en los
|
|
91
|
+
inputs de mobile (CLAUDE.md, «Responsive mobile»). Hoy no hay forma de cambiarlo desde afuera.
|
|
92
|
+
- **El teclado no trae la lupa**: el `<input>` es `type="text"` sin `enterkeyhint="search"` (la regla
|
|
93
|
+
de la app para buscadores, CLAUDE.md «La lupa del teclado»).
|
|
94
|
+
- **El campo no tiene nombre accesible**: solo placeholder, sin `aria-label`.
|
|
95
|
+
- **Al abrir el menú el foco no entra al buscador**: quien quiera escribir de una tiene que enfocarlo
|
|
96
|
+
a mano.
|
|
97
|
+
- El `<input>` no tiene `(keydown)`: Enter no hace nada salvo que quien lo usa lo escuche.
|
|
98
|
+
|
|
99
|
+
## Ejemplo
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<sui-dropdown-menu #menu="suiDropdownMenu" [minWidth]="280">
|
|
103
|
+
<sui-button trigger variant="stroke" color="neutral">Asignar repartidor</sui-button>
|
|
104
|
+
<ng-template suiDropdownPanel>
|
|
105
|
+
<sui-dropdown-search [(value)]="busqueda" placeholder="Buscar repartidor…" />
|
|
106
|
+
@for (r of repartidoresFiltrados(); track r.uid) {
|
|
107
|
+
<sui-dropdown-item [label]="r.nombre" (clicked)="menu.close(); asignar(r.uid)">
|
|
108
|
+
<sui-avatar slot="leading" size="xs" [src]="r.avatarUrl" [initials]="r.nombre.charAt(0)" />
|
|
109
|
+
</sui-dropdown-item>
|
|
110
|
+
}
|
|
111
|
+
</ng-template>
|
|
112
|
+
</sui-dropdown-menu>
|
|
113
|
+
```
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-filter
|
|
3
|
+
selector: sui-filter-bar, sui-filter-item, sui-filter-panel-header, sui-filter-panel-footer
|
|
4
|
+
resumen: Piezas de filtros de una lista o tabla del panel; la barra horizontal (buscador, vista, orden) y el panel vertical (fila, cabecera con «Limpiar», pie con «Limpiar» / «Aplicar»)
|
|
5
|
+
alternativas: [sui-segmented-control (2 a 4 vistas que se aplican al instante), sui-compact-select (un filtro suelto), sui-tab-chips (filtros rápidos con chips), sui-activity-filter (filtro del feed de actividad), sui-modal-sheet (el panel de filtros en mobile)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-filter
|
|
9
|
+
|
|
10
|
+
Story: [Organisms/Filter Bar](https://sui-design-system.web.app/?path=/docs/organisms-filter-bar--docs)
|
|
11
|
+
|
|
12
|
+
Son **cuatro componentes** en una carpeta, sin uno llamado `sui-filter`:
|
|
13
|
+
|
|
14
|
+
- `sui-filter-bar`: la **barra horizontal** arriba de una tabla. Es solo layout: un lado
|
|
15
|
+
izquierdo (`slot="start"`) y uno derecho (`slot="end"`); los controles se proyectan.
|
|
16
|
+
- `sui-filter-item`: una **fila** del panel vertical (una categoría de filtro: «Estado», «Local»).
|
|
17
|
+
- `sui-filter-panel-header`: la **cabecera** del panel, con un link «Limpiar» opcional.
|
|
18
|
+
- `sui-filter-panel-footer`: el **pie** del panel, con «Limpiar» y «Aplicar».
|
|
19
|
+
|
|
20
|
+
El panel vertical **no existe como contenedor**: se arma con header + items + footer dentro de un
|
|
21
|
+
`sui-dropdown-menu` (web) o de un `sui-modal-sheet` (mobile).
|
|
22
|
+
|
|
23
|
+
## Cuándo usarlo / cuándo no
|
|
24
|
+
|
|
25
|
+
**Úsalo** en las listas y tablas del panel del local o del super admin que necesitan filtrar
|
|
26
|
+
(pedidos, productos, repartidores): la barra arriba con el buscador, la vista y el orden; el panel
|
|
27
|
+
cuando hay varias categorías de filtro que se combinan y se aplican juntas.
|
|
28
|
+
|
|
29
|
+
**No lo uses** para:
|
|
30
|
+
|
|
31
|
+
- 2 a 4 vistas que se aplican al tocar (Hoy · Semana · Mes): `sui-segmented-control` solo, sin barra.
|
|
32
|
+
- Un único filtro: un `sui-compact-select` o `sui-select` suelto.
|
|
33
|
+
- Filtros rápidos de un toque en mobile (Todos · Pendientes · Entregados): `sui-tab-chips`.
|
|
34
|
+
- El feed de actividad: `sui-activity-filter`.
|
|
35
|
+
|
|
36
|
+
Regla corta: **barra arriba de una tabla → filter-bar · varias categorías que se aplican juntas →
|
|
37
|
+
panel (header + items + footer) · un filtro suelto → compact-select.**
|
|
38
|
+
|
|
39
|
+
## Qué variante usar
|
|
40
|
+
|
|
41
|
+
> **La guía del DS no tiene página de filtros**: estas piezas salen solo del Figma del DS
|
|
42
|
+
> («Filters»: HorizontalFilter y VerticalFilter). Todo lo de abajo es **propuesta, falta
|
|
43
|
+
> aprobación**, salvo lo marcado canon.
|
|
44
|
+
>
|
|
45
|
+
> Uso medido el 03-10-2026 en `master-pedidos/src/app` (misma exclusión que `sui-dropdown-menu`):
|
|
46
|
+
> **0 usos**. Solo aparecen en la vitrina `master-preview`.
|
|
47
|
+
|
|
48
|
+
### `sui-filter-bar`
|
|
49
|
+
|
|
50
|
+
| variant | cuándo | fuente |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `table` (default) | Arriba de una tabla: a la izquierda la vista (`sui-segmented-control`), a la derecha el buscador (`sui-input type="search"`) y el orden (`sui-select` o `sui-compact-select`). | propuesta (composición del Figma) |
|
|
53
|
+
| `calendar` | Arriba de una vista por fechas: a la izquierda «Hoy» + el rango (`sui-button-group`), a la derecha el buscador. | propuesta (composición del Figma) |
|
|
54
|
+
|
|
55
|
+
`variant` solo marca el host (`data-sui-variant`): **no cambia el layout**. Lo que se ve lo deciden
|
|
56
|
+
los controles que se proyectan.
|
|
57
|
+
|
|
58
|
+
### El panel: `sui-filter-item` + header + footer
|
|
59
|
+
|
|
60
|
+
| pieza | regla | fuente |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `sui-filter-item` `active` | La categoría abierta (la que muestra sus opciones al lado): fondo gris, texto negro, flecha a la derecha. | propuesta (estados del Figma) |
|
|
63
|
+
| `sui-filter-item` `leftIcon` | Un ícono por categoría si ayuda a ubicarla («Estado», «Local», «Repartidor»). | propuesta |
|
|
64
|
+
| `sui-filter-panel-header` `clear` | Pásale texto (`clear="Limpiar"`), no `true`: con `true` dice «Clear». | canon (CANON-SUI §5: copy en español) |
|
|
65
|
+
| `sui-filter-panel-footer` | «Limpiar» a la izquierda (`stroke neutral`), «Aplicar» a la derecha (`filled primary`), mitad y mitad. | canon (orden de `sui-modal-footer`, CLAUDE.md «Botones») |
|
|
66
|
+
| «Limpiar» en header **y** footer | Elegir uno. Con el footer, el header va sin link. | propuesta |
|
|
67
|
+
| `applyButton` deshabilitado | `{ label: 'Aplicar', disabled: !cambio() }` mientras no se toca nada. | propuesta |
|
|
68
|
+
|
|
69
|
+
## Tamaño en mobile y en web
|
|
70
|
+
|
|
71
|
+
Ninguna pieza cambia sola en mobile, salvo la barra, que apila sus dos lados por debajo de 640 px.
|
|
72
|
+
|
|
73
|
+
| pieza | mobile | web | fuente |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| `sui-filter-bar` | sin tamaño; los controles adentro van `large` (48) | sin tamaño; los controles `small` (36) | propuesta (igual que `sui-compact-select` en una barra) |
|
|
76
|
+
| `sui-filter-item` | 36 de alto, no se puede cambiar: debajo de los 44 del dedo | 36 | propuesta |
|
|
77
|
+
| `sui-filter-panel-footer` | `medium` (40) es lo más grande que tiene; en mobile, mejor `sui-modal-footer` (48) dentro de un `sui-modal-sheet` | `small` (36, default) | canon para el 48 de mobile (CLAUDE.md: footer de modal `large`); el resto, propuesta |
|
|
78
|
+
| `sui-filter-panel-header` | sin tamaño | sin tamaño | — |
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<sui-filter-bar>
|
|
82
|
+
<sui-segmented-control slot="start" … />
|
|
83
|
+
<div slot="end">
|
|
84
|
+
<sui-input type="search" [size]="bp.isMobile() ? 'large' : 'small'" placeholder="Buscar pedido…" … />
|
|
85
|
+
</div>
|
|
86
|
+
</sui-filter-bar>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
<!-- api:inicio -->
|
|
92
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
93
|
+
|
|
94
|
+
### `sui-filter-bar`
|
|
95
|
+
|
|
96
|
+
| input | tipo | default |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `variant` | `'calendar' \| 'table'` | `'table'` |
|
|
99
|
+
|
|
100
|
+
Outputs: ninguno.
|
|
101
|
+
|
|
102
|
+
### `sui-filter-item`
|
|
103
|
+
|
|
104
|
+
| input | tipo | default |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `active` | `boolean` | `false` |
|
|
107
|
+
| `disabled` | `boolean` | `false` |
|
|
108
|
+
| `leftIcon` | `string \| null` | `null` |
|
|
109
|
+
| `activeIcon` | `string` | `'arrow-right-s-line'` |
|
|
110
|
+
|
|
111
|
+
| output | payload |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `selected` | `Event` |
|
|
114
|
+
|
|
115
|
+
### `sui-filter-panel-footer`
|
|
116
|
+
|
|
117
|
+
| input | tipo | default |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `clearButton` | `SuiFooterButtonConfig \| string` | `'Clear'` |
|
|
120
|
+
| `applyButton` | `SuiFooterButtonConfig \| string` | `'Apply'` |
|
|
121
|
+
| `size` | `'small' \| 'medium'` | `'small'` |
|
|
122
|
+
|
|
123
|
+
| output | payload |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `clear` | `void` |
|
|
126
|
+
| `apply` | `void` |
|
|
127
|
+
|
|
128
|
+
### `sui-filter-panel-header`
|
|
129
|
+
|
|
130
|
+
| input | tipo | default |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `label` | `string` | `''` |
|
|
133
|
+
| `leftIcon` | `string \| null` | `null` |
|
|
134
|
+
| `clear` | `string \| boolean` | `false` |
|
|
135
|
+
|
|
136
|
+
| output | payload |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `clearClick` | `void` |
|
|
139
|
+
<!-- api:fin -->
|
|
140
|
+
|
|
141
|
+
Slots de `sui-filter-bar`: `[slot=start]`, `[slot=end]` y el contenido por defecto (fila única si no
|
|
142
|
+
se usan los slots). El label de `sui-filter-item` es su contenido proyectado. `clearButton` y
|
|
143
|
+
`applyButton` aceptan un string o `SuiFooterButtonConfig` (`{ label, disabled? }`, la misma interfaz
|
|
144
|
+
de `sui-drawer-footer`).
|
|
145
|
+
|
|
146
|
+
## Trampas
|
|
147
|
+
|
|
148
|
+
- **Los textos por defecto están en inglés**: «Clear» y «Apply» en el footer, «Clear» en el header con
|
|
149
|
+
`clear=true`. Pásalos siempre.
|
|
150
|
+
- **La barra apila a 640 px; la app cambia a mobile a 767.** Entre 641 y 767 px la app ya está en
|
|
151
|
+
mobile y la barra sigue en una fila.
|
|
152
|
+
- **Varios controles en un lado van en un contenedor.** Cada slot es una fila con 12 px de separación,
|
|
153
|
+
pero un solo elemento por `slot` queda más ordenado (`<div slot="end">`), como en el Figma.
|
|
154
|
+
- **`sui-filter-item` tiene `role="option"`**: el contenedor tiene que llevar `role="listbox"`.
|
|
155
|
+
- **`active` no es «elegido»**: marca la categoría abierta, no un valor aplicado. Para decir que una
|
|
156
|
+
categoría tiene un filtro puesto no hay prop (un `sui-badge` en el label es lo más cercano).
|
|
157
|
+
- **El footer separa sus botones 16 px** (del Figma); el canon de la app entre botones hermanos es 12
|
|
158
|
+
(CANON-SUI §0, `sui-modal-footer`).
|
|
159
|
+
- El footer no llega a `large`: en mobile queda más chico que el footer de cualquier modal.
|
|
160
|
+
- `disabled` del item usa opacidad (no está en el Figma).
|
|
161
|
+
|
|
162
|
+
## Ejemplo
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<!-- Panel de filtros en web, anclado a un botón «Filtros» -->
|
|
166
|
+
<sui-dropdown-menu #filtros="suiDropdownMenu" [minWidth]="320">
|
|
167
|
+
<sui-button trigger variant="stroke" color="neutral" size="small">Filtros</sui-button>
|
|
168
|
+
<ng-template suiDropdownPanel>
|
|
169
|
+
<sui-filter-panel-header label="Filtros" leftIcon="equalizer-line" />
|
|
170
|
+
<div role="listbox" aria-label="Categorías de filtro">
|
|
171
|
+
<sui-filter-item leftIcon="checkbox-circle-line" [active]="cat() === 'estado'"
|
|
172
|
+
(selected)="cat.set('estado')">Estado</sui-filter-item>
|
|
173
|
+
<sui-filter-item leftIcon="store-2-line" [active]="cat() === 'local'"
|
|
174
|
+
(selected)="cat.set('local')">Local</sui-filter-item>
|
|
175
|
+
</div>
|
|
176
|
+
<sui-filter-panel-footer clearButton="Limpiar"
|
|
177
|
+
[applyButton]="{ label: 'Aplicar', disabled: !cambio() }"
|
|
178
|
+
(clear)="limpiar()" (apply)="filtros.close(); aplicar()" />
|
|
179
|
+
</ng-template>
|
|
180
|
+
</sui-dropdown-menu>
|
|
181
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sui-href
|
|
3
|
+
selector: a[suiHref]
|
|
4
|
+
resumen: Directiva para los <a> del DS que reciben un href configurable; los links internos navegan con el Router sin recargar la app
|
|
5
|
+
alternativas: [routerLink (link interno escrito en la plantilla de la app), sui-link-button (acción con forma de link, ya usa suiHref por dentro)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# sui-href
|
|
9
|
+
|
|
10
|
+
Story: [Utilities/Href](https://sui-design-system.web.app/?path=/docs/utilities-href--docs)
|
|
11
|
+
|
|
12
|
+
## Cuándo usarlo / cuándo no
|
|
13
|
+
|
|
14
|
+
**Úsalo** dentro de un componente del DS que recibe un `href` por input y lo pinta en un `<a>`:
|
|
15
|
+
en vez de `[attr.href]="href()"`, `[suiHref]="href()"`. Así un `href="/cliente/pedidos"` que
|
|
16
|
+
viene de la app navega **sin recargar el WebView** (en Capacitor, una recarga pierde el estado y
|
|
17
|
+
tarda segundos) y uno externo se abre normal.
|
|
18
|
+
|
|
19
|
+
Ya lo usan `sui-link-button`, `sui-compact-button`, `sui-fancy-button`, `sui-social-button`,
|
|
20
|
+
`sui-breadcrumb-item`, `sui-topbar-item`, `sui-brand-logo`, `sui-app-store-badge`, `sui-rating` y
|
|
21
|
+
`sui-progress-bar-label`.
|
|
22
|
+
|
|
23
|
+
**No lo uses** para:
|
|
24
|
+
|
|
25
|
+
- Un link interno que escribes en una plantilla de la app: `routerLink`. Es lo que usa la app
|
|
26
|
+
(98 usos, 0 de `suiHref` el 03-10-2026) y además marca el link activo.
|
|
27
|
+
- Un botón que ejecuta algo: `sui-button` o `sui-link-button` con `(clicked)`.
|
|
28
|
+
|
|
29
|
+
## Qué variante usar
|
|
30
|
+
|
|
31
|
+
> **No está en la guía del DS: es una utilidad de SUI**, sin Figma ni variantes.
|
|
32
|
+
|
|
33
|
+
No tiene variantes. Lo que decide qué hace es la URL:
|
|
34
|
+
|
|
35
|
+
| `suiHref` | qué hace al tocarlo | fuente |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| empieza con `/` (no `//`) | `router.navigateByUrl()`: navegación dentro de la app, conserva query y `#` | componente |
|
|
38
|
+
| `https://…`, `mailto:`, `tel:`, `#…`, `//host` | lo que haga el navegador | componente |
|
|
39
|
+
| cualquiera, con `target` distinto de `_self` | lo que haga el navegador (nueva pestaña) | componente |
|
|
40
|
+
| cualquiera, con cmd/ctrl/shift/alt o botón del medio | lo que haga el navegador | componente |
|
|
41
|
+
| `null` o `''` | el `<a>` queda **sin** `href` (no navegable) | componente |
|
|
42
|
+
|
|
43
|
+
## Tamaño en mobile y en web
|
|
44
|
+
|
|
45
|
+
No aplica: es una directiva, no dibuja nada. El tamaño es el del componente que la usa.
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
<!-- api:inicio -->
|
|
50
|
+
<!-- generada: no editar a mano — `npm run docs` -->
|
|
51
|
+
|
|
52
|
+
| input | tipo | default |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `suiHref` | `string \| null` | `null` |
|
|
55
|
+
|
|
56
|
+
Outputs: ninguno.
|
|
57
|
+
<!-- api:fin -->
|
|
58
|
+
|
|
59
|
+
Solo se aplica a `<a>` (`selector: a[suiHref]`).
|
|
60
|
+
|
|
61
|
+
## Trampas
|
|
62
|
+
|
|
63
|
+
- **El atributo `href` real queda puesto**: el clic del medio, «copiar enlace» y «abrir en
|
|
64
|
+
pestaña nueva» siguen funcionando. Solo el clic normal se intercepta.
|
|
65
|
+
- **Sin Router no intercepta nada**: en Storybook o en una app sin `provideRouter` es un `href`
|
|
66
|
+
común y corriente (recarga la página).
|
|
67
|
+
- **Si el componente ya hizo `preventDefault()`** en su propio `(click)`, la directiva no navega.
|
|
68
|
+
Útil para un link que a veces solo emite un evento.
|
|
69
|
+
- **No marca el link activo** (`routerLinkActive` no lo ve). Para menús de la app, `routerLink`.
|
|
70
|
+
- Un `href` relativo sin `/` inicial (`pedidos/123`) **no** se intercepta: lo resuelve el
|
|
71
|
+
navegador contra la URL actual y recarga. Pasa rutas absolutas.
|
|
72
|
+
|
|
73
|
+
## Ejemplo
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<!-- Dentro del template de un componente del DS -->
|
|
77
|
+
<a class="sui-link-button" [suiHref]="href()" [attr.target]="target()">
|
|
78
|
+
<ng-content />
|
|
79
|
+
</a>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<!-- Desde la app: el componente del DS ya lo resuelve -->
|
|
84
|
+
<sui-link-button href="/cliente/pedidos">Ver mis pedidos</sui-link-button>
|
|
85
|
+
```
|