build method
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,
);
},
),
);
}