dialogViewport function

BloomNode dialogViewport()

Root viewport component for rendering the active modal dialog.

Mount this once near the root of the application.

Implementation

BloomNode dialogViewport() {
  return Live(() {
    final config = activeDialog.value;
    if (config == null) return const Fragment(children: []);

    final hasFooter = config.confirmLabel != null || config.cancelLabel != null;

    return Div(
      className:
          'fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4',
      onClick: (_) => closeDialog(),
      children: [
        Div(
          attrs: const {
            'role': 'dialog',
            'aria-modal': 'true',
            'aria-labelledby': 'dialog-title',
          },
          className:
              'relative bg-[var(--card)] border border-[var(--border)] rounded-[var(--radius-lg)] '
              'p-6 shadow-[var(--shadow-overlay)] max-w-lg w-full flex flex-col gap-4',
          onClick: (BloomEvent e) => e.stopPropagation(),
          children: [
            // Top close button
            El(
              'button',
              attrs: const {
                'type': 'button',
                'aria-label': 'Close dialog',
              },
              className:
                  'absolute top-4 right-4 text-[var(--text-muted)] hover:text-[var(--text)] '
                  'p-1 rounded-[var(--radius-sm)] transition-colors cursor-pointer',
              onClick: (_) => closeDialog(),
              children: [
                uiIcon('x', className: 'w-4 h-4'),
              ],
            ),
            // Header
            Div(
              className: 'flex flex-col gap-1 pr-6',
              children: [
                H3(
                  attrs: const {'id': 'dialog-title'},
                  className: 'text-lg font-semibold text-[var(--text)] leading-none',
                  text: config.title,
                ),
                if (config.description != null && config.description!.isNotEmpty)
                  P(
                    className: 'text-sm text-[var(--text-muted)] leading-relaxed mt-1',
                    text: config.description!,
                  ),
              ],
            ),
            // Custom body
            if (config.body != null) config.body!,
            // Footer actions
            if (hasFooter)
              Div(
                className: 'flex items-center justify-end gap-3 mt-2 pt-2',
                children: [
                  if (config.cancelLabel != null)
                    button(
                      text: config.cancelLabel!,
                      variant: ButtonVariant.secondary,
                      onClick: (_) => closeDialog(),
                    ),
                  if (config.confirmLabel != null)
                    button(
                      text: config.confirmLabel!,
                      variant: config.destructive
                          ? ButtonVariant.destructive
                          : ButtonVariant.primary,
                      onClick: (_) {
                        final action = config.onConfirm;
                        activeDialog.value = null;
                        action?.call();
                      },
                    ),
                ],
              ),
          ],
        ),
      ],
    );
  });
}