import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; class SpecTable { const SpecTable({ required this.columns, required this.rows, this.columnWidth, }); final List columns; final List> rows; final double? columnWidth; } class SpecSection { const SpecSection({ required this.title, this.items = const [], this.description, this.table, }); final String title; final List items; final String? description; final SpecTable? table; } class SpecPage extends StatelessWidget { const SpecPage({ super.key, required this.title, required this.summary, required this.sections, this.trailing, }); final String title; final String summary; final List sections; final Widget? trailing; @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return SelectionArea( child: SingleChildScrollView( padding: const EdgeInsets.all(32), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 1200), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: theme.textTheme.h2), const SizedBox(height: 12), Text(summary, style: theme.textTheme.lead), ], ), ), if (trailing != null) ...[ const SizedBox(width: 24), trailing!, ], ], ), const SizedBox(height: 32), ...sections.map( (section) => Padding( padding: const EdgeInsets.only(bottom: 24), child: ShadCard( title: Text( section.title, style: theme.textTheme.h3.copyWith( color: theme.colorScheme.foreground, ), ), description: section.description == null ? null : Text( section.description!, style: theme.textTheme.muted, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (section.items.isNotEmpty) ...[ for (final item in section.items) Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(top: 6), child: Icon( LucideIcons.dot, size: 10, color: theme.colorScheme.mutedForeground, ), ), const SizedBox(width: 8), Expanded( child: Text( item, style: theme.textTheme.p, ), ), ], ), ), ], if (section.table != null) ...[ if (section.items.isNotEmpty) const SizedBox(height: 16), _SpecTableView(table: section.table!), ], ], ), ), ), ), ], ), ), ), ), ); } } class _SpecTableView extends StatelessWidget { const _SpecTableView({required this.table}); final SpecTable table; @override Widget build(BuildContext context) { final headerCells = table.columns .map((column) => ShadTableCell.header(child: Text(column))) .toList(growable: false); final rowCells = table.rows .map( (row) => row .map((cell) => ShadTableCell(child: Text(cell))) .toList(growable: false), ) .toList(growable: false); final rowCount = table.rows.length; final baseHeight = 56.0; // default row height with some breathing room final height = (rowCount + 1) * baseHeight; return SizedBox( height: height, child: ShadTable.list( header: headerCells, children: rowCells, columnSpanExtent: (index) => FixedTableSpanExtent(table.columnWidth ?? 160), ), ); } }