feat(web): ventana de formulario SBO (toolbar, título con estado, pestañas, menús y barra de estado)

- sbo-window: título con insignia de estado, cuerpo, zona de acciones
  (sboActions / sboActionsEnd) y barra de estado ok / warn / err / info con
  role="status".
- sbo-toolbar con botones .tb y sbo-icon (SVG en línea).
- sbo-tabs / sbo-tab: paneles ocultos conservan su estado, [(selected)],
  flechas izquierda/derecha con foco, roles y aria-controls.
- sbo-menu-button: lista de acciones ("Copiar de/a") que abre hacia arriba o
  abajo; Esc y clic fuera la cierran; opciones deshabilitables.
- sbo-badge compartido con las listas (data-table lo usa); tono 'info'.
- Verificado en Chrome contra el mockup oferta_venta.html: misma estructura,
  pestañas por clic y teclado, menú hacia arriba, estados ok/err.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-13 20:09:41 -05:00
1 parent fad66abec4
commit 26f0649da9
16 files changed
+768 -25

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import type { Tone } from '../list/list.types';
import { Badge } from './badge';
export type StatusKind = 'info' | 'ok' | 'warn' | 'err';
export interface StatusMessage {
kind: StatusKind;
text: string;
}
export interface WindowBadge {
label: string;
tone: Tone;
}
const GLYPH: Record<StatusKind, string> = { info: 'ℹ', ok: '✓', warn: '!', err: '✕' };
/**
* Ventana de formulario SBO: título con insignia de estado, cuerpo, zona de acciones y barra de estado.
*
* Contenido: el cuerpo va como contenido normal; las acciones con `sboActions` (izquierda) y
* `sboActionsEnd` (derecha, ej. "Copiar de/a").
*/
@Component({
selector: 'sbo-window',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [Badge],
templateUrl: './window.html',
styleUrl: './window.css',
})
export class SboWindow {
readonly title = input.required<string>();
readonly badge = input<WindowBadge>();
readonly status = input<StatusMessage>({ kind: 'info', text: '' });
protected readonly glyph = computed(() => GLYPH[this.status().kind]);
}