sheetViewport function

BloomNode sheetViewport()

Root viewport component for rendering active slide-over sheets and bottom drawers.

Mount this once near the root of the application.

Implementation

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

    final side = config.side;

    final sideClasses = switch (side) {
      'left' =>
        'inset-y-0 left-0 h-full w-3/4 max-w-sm border-r border-[var(--border)]',
      'top' => 'inset-x-0 top-0 w-full border-b border-[var(--border)]',
      'bottom' =>
        'inset-x-0 bottom-0 w-full max-h-[85vh] rounded-t-[var(--radius-lg)] border-t border-[var(--border)]',
      _ =>
        'inset-y-0 right-0 h-full w-3/4 max-w-sm border-l border-[var(--border)]',
    };

    return Div(
      className: 'fixed inset-0 bg-black/60 backdrop-blur-sm z-50 select-none',
      onClick: (_) => closeSheet(),
      children: [
        Div(
          attrs: const {
            'role': 'dialog',
            'aria-modal': 'true',
          },
          className: cn([
            'fixed bg-[var(--card)] p-6 shadow-[var(--shadow-overlay)] flex flex-col gap-4 z-50 overflow-y-auto select-text',
            sideClasses,
          ]),
          onClick: (BloomEvent e) => e.stopPropagation(),
          children: [
            // Drawer handle indicator on bottom sheet
            if (side == 'bottom')
              Div(
                className: 'mx-auto -mt-2 mb-2 h-1 w-12 rounded-full bg-[var(--border)]',
              ),
            // Header
            Div(
              className: 'flex items-start justify-between gap-4',
              children: [
                Div(
                  className: 'flex flex-col gap-1',
                  children: [
                    H3(
                      className:
                          'text-base font-semibold text-[var(--text)] leading-tight',
                      text: config.title,
                    ),
                    if (config.description != null &&
                        config.description!.isNotEmpty)
                      P(
                        className: 'text-xs text-[var(--text-muted)] leading-relaxed',
                        text: config.description!,
                      ),
                  ],
                ),
                if (config.showCloseButton)
                  El(
                    'button',
                    attrs: const {
                      'type': 'button',
                      'aria-label': 'Close panel',
                    },
                    className:
                        'text-[var(--text-muted)] hover:text-[var(--text)] p-1 rounded-[var(--radius-sm)] transition-colors cursor-pointer',
                    onClick: (_) => closeSheet(),
                    children: [uiIcon('x', className: 'w-4 h-4')],
                  ),
              ],
            ),
            // Custom body
            if (config.body != null) config.body!,
          ],
        ),
      ],
    );
  });
}