mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-13 17:30:58 +01:00
unified-design-system/ froze the token layer as prose -- 1220 lines of README, tokens.md and annotated HTML, and zero importable code. Every Flutter repo transcribed the token table by hand, so the "shared" palette was enforced only by discipline, and had already drifted: 10 divergent theme.dart copies (todo and untools byte-identical, the other 8 each unique), ~318 raw color literals, 4 hand-rolled AppStatusColors extensions with inconsistent field sets, ~35 inlined AlertDialogs, ~38 raw SnackBar sites with no helper anywhere. This promotes the token layer into an importable component layer, so the design system is enforced by the compiler and the package manager instead. The components own the tokens: a consumer has nothing local left to edit. Scales are the six-repo consensus, which is also what tokens.md specifies; drifted repos get corrected to this rather than the reverse. Themes are built from explicit ColorSchemes, never fromSeed -- the palette is hand-picked for contrast, and secondary/tertiary are set explicitly so SegmentedButton cannot fall back to stock Material teal. Widgets are extracted only where two or more repos already had a structurally similar implementation; SectionHeader reconciles untools' trailing-action variant with billsplit's padded one as an opt-in union, so either caller can adopt it with no visual change. 63 tests, 102/102 lines covered, gated at 100% on pre-push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL
62 lines
1.9 KiB
Dart
62 lines
1.9 KiB
Dart
/// A labelled divider between groups of controls in a long screen.
|
|
library;
|
|
|
|
import 'package:design_system/src/tokens.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// A section title, optionally with a trailing action.
|
|
///
|
|
/// Reconciles the fleet's variants, which agreed on `titleMedium` and
|
|
/// differed only in what they wrapped around it: `untools`' `_SectionHeading`
|
|
/// supported a trailing action and no padding; `billsplit`'s `_SectionHeader`
|
|
/// had padding and no action. This is the union — both are opt-in, so either
|
|
/// caller can adopt it without a visual change.
|
|
class SectionHeader extends StatelessWidget {
|
|
/// Creates a section header.
|
|
const SectionHeader(
|
|
this.title, {
|
|
this.action,
|
|
this.padding,
|
|
super.key,
|
|
});
|
|
|
|
/// The section's title.
|
|
final String title;
|
|
|
|
/// Optional trailing control, right-aligned on the same baseline —
|
|
/// typically an "Add" or "Clear" button scoped to this section.
|
|
final Widget? action;
|
|
|
|
/// Outer padding. Defaults to none, so the header inherits whatever the
|
|
/// surrounding list already applies; pass [defaultPadding] for a header
|
|
/// sitting directly in an unpadded `ListView`.
|
|
final EdgeInsetsGeometry? padding;
|
|
|
|
/// Padding for a header placed directly in an unpadded scroll view: full
|
|
/// gutters, generous above, tight below so the title groups with the
|
|
/// content it labels rather than floating between two sections.
|
|
static const EdgeInsets defaultPadding = EdgeInsets.fromLTRB(
|
|
AppSpacing.md,
|
|
AppSpacing.md,
|
|
AppSpacing.md,
|
|
AppSpacing.xs,
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final row = Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
|
|
),
|
|
?action,
|
|
],
|
|
);
|
|
final padding = this.padding;
|
|
if (padding == null) {
|
|
return row;
|
|
}
|
|
return Padding(padding: padding, child: row);
|
|
}
|
|
}
|