build method

  1. @override
Widget build(
  1. BuildContext context
)
override

Composes headers, editable rows, and aligned footer cells using CarpenterTheme. Width constraints determine whether a horizontal scroll view is needed; vertical scrolling belongs to the parent.

Implementation

@override
Widget build(BuildContext context) {
  final theme = CarpenterTheme.of(context);
  final rowGap = context.units(theme.spacing.small);
  final horizontal = context.units(theme.spacing.tableHorizontal);
  final vertical = context.units(theme.spacing.tableVertical);
  final borderWidth = context.units(theme.shapes.tableBorderWidth);
  final selectedBackground = theme.selection
      .resolve(
        role: SelectionColorRole.primary,
        selected: true,
        states: const <WidgetState>{},
      )
      .background;

  return Semantics(
    container: true,
    label: semanticLabel,
    child: LayoutBuilder(
      builder: (context, constraints) {
        final minWidth = context.units(minimumWidth);
        final viewportWidth = constraints.maxWidth.isFinite
            ? constraints.maxWidth
            : minWidth;
        final contentWidth = math.max(viewportWidth, minWidth);

        final table = SizedBox(
          width: contentWidth,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.min,
            children: [
              if (headerActions.isNotEmpty) ...[
                Align(
                  alignment: AlignmentDirectional.centerEnd,
                  child: Wrap(
                    spacing: rowGap,
                    runSpacing: rowGap,
                    children: headerActions,
                  ),
                ),
                SizedBox(height: rowGap),
              ],
              Container(
                color: theme.surface.subtle,
                padding: EdgeInsets.symmetric(
                  horizontal: horizontal,
                  vertical: vertical,
                ),
                child: _row(
                  context,
                  columns
                      .map(
                        (column) => CarpenterText.label(
                          column.header,
                          emphasis: TypographyEmphasis.strong,
                          semanticsLabel: column.semanticLabel,
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                        ),
                      )
                      .toList(growable: false),
                ),
              ),
              if (items.isEmpty)
                Padding(
                  padding: EdgeInsets.symmetric(vertical: vertical * 3),
                  child: Center(
                    child: CarpenterText.body(
                      emptyMessage,
                      colorRole: ContentColorRole.secondary,
                    ),
                  ),
                )
              else
                for (final item in items)
                  GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    onTap: onRowSelected == null
                        ? null
                        : () => onRowSelected!(item),
                    onDoubleTap: onRowActivated == null
                        ? null
                        : () => onRowActivated!(item),
                    child: Container(
                      decoration: BoxDecoration(
                        color: selected?.call(item) == true
                            ? selectedBackground
                            : theme.surface.base,
                        border: Border(
                          bottom: BorderSide(
                            color: theme.overlay.border,
                            width: borderWidth,
                          ),
                        ),
                      ),
                      padding: EdgeInsets.symmetric(
                        horizontal: horizontal,
                        vertical: vertical,
                      ),
                      child: _row(
                        context,
                        columns
                            .map(
                              (column) => column.cellBuilder(context, item),
                            )
                            .toList(growable: false),
                      ),
                    ),
                  ),
              if (footerCells.isNotEmpty)
                Container(
                  color: theme.surface.subtle,
                  padding: EdgeInsets.symmetric(
                    horizontal: horizontal,
                    vertical: vertical,
                  ),
                  child: _row(
                    context,
                    columns
                        .map(
                          (column) =>
                              footerCells[column.id]?.call(context, items) ??
                              const SizedBox.shrink(),
                        )
                        .toList(growable: false),
                  ),
                ),
            ],
          ),
        );

        if (contentWidth <= viewportWidth) return table;
        return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: table,
        );
      },
    ),
  );
}