@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.
Files changed (42) hide show
  1. package/docs/INDEX.md +25 -30
  2. package/docs/sui-accordion/README.md +171 -0
  3. package/docs/sui-activity-feed/README.md +155 -0
  4. package/docs/sui-activity-filter/README.md +100 -0
  5. package/docs/sui-button/README.md +19 -0
  6. package/docs/sui-command-menu-footer/README.md +100 -0
  7. package/docs/sui-command-menu-item/README.md +140 -0
  8. package/docs/sui-command-menu-search/README.md +105 -0
  9. package/docs/sui-compact-select/README.md +4 -1
  10. package/docs/sui-compact-select-for-input/README.md +3 -0
  11. package/docs/sui-counter-input/README.md +3 -0
  12. package/docs/sui-datepicker/README.md +4 -0
  13. package/docs/sui-dropdown-caption/README.md +92 -0
  14. package/docs/sui-dropdown-item/README.md +163 -0
  15. package/docs/sui-dropdown-menu/README.md +196 -0
  16. package/docs/sui-dropdown-search/README.md +113 -0
  17. package/docs/sui-filter/README.md +181 -0
  18. package/docs/sui-href/README.md +85 -0
  19. package/docs/sui-img-retry/README.md +107 -0
  20. package/docs/sui-input/README.md +3 -0
  21. package/docs/sui-modal/README.md +238 -0
  22. package/docs/sui-modal-footer/README.md +178 -0
  23. package/docs/sui-modal-header/README.md +115 -0
  24. package/docs/sui-modal-sheet/README.md +170 -0
  25. package/docs/sui-page-header/README.md +123 -0
  26. package/docs/sui-segmented-control/README.md +3 -0
  27. package/docs/sui-select/README.md +5 -2
  28. package/docs/sui-sidebar/README.md +217 -0
  29. package/docs/sui-switch-label/README.md +3 -0
  30. package/docs/sui-tab-chips/README.md +5 -2
  31. package/docs/sui-table/README.md +264 -0
  32. package/docs/sui-tag-input/README.md +3 -0
  33. package/docs/sui-textarea/README.md +3 -0
  34. package/docs/sui-time-picker/README.md +3 -0
  35. package/docs/sui-topbar/README.md +153 -0
  36. package/docs/sui-topbar-button/README.md +95 -0
  37. package/docs/sui-topbar-item/README.md +97 -0
  38. package/docs/sui-topbar-user/README.md +83 -0
  39. package/fesm2022/ajuarezso-sui-design-system.mjs +32 -29
  40. package/fesm2022/ajuarezso-sui-design-system.mjs.map +1 -1
  41. package/package.json +1 -1
  42. 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
+ ```