mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 20:07:02 +01:00
CLAUDE.md 925 -> 165. 789 of those lines were a verbatim paste of upstream's flutter/flutter rules.md -- 85% of an always-loaded file given over to a document nobody edits. It moves to five topic files under docs/, linked rather than @-imported: inlining it back would pass the cap while keeping it in context on every turn, which is the cost the cap exists to remove. Every `###`/`####` heading in the upstream block survives the split; the two that do not are its own wrapper headings, replaced by the per-file titles (verified by diffing the sorted heading lists). desktop-performance-findings.md 278 -> 73 + 216. It was already narratively two-part; the author signposted "Part 2" explicitly at the seam. Also updates two things that had drifted: the suite is 313 tests at 100% coverage (not 238), and Conventions now documents the cap, where the gate lives, and the two patterns used to split without breaking callers (re-export barrels, and `part` files where a private State must stay in one library). `bash ~/utils/scripts/check_file_length.sh --all` now exits 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.4 KiB
6.4 KiB
Flutter AI rules — UI: theming, layout and overlays
Material theming, WidgetStateProperty, layout, and overlays.
Verbatim from flutter/flutter docs/rules/rules.md, split across four files only to keep each under the repo's 250-line cap. Re-fetch periodically to stay current; do not hand-edit.
- Responsiveness: Use
LayoutBuilderorMediaQueryto create responsive UIs. - Text: Use
Theme.of(context).textThemefor text styles. - Text Fields: Configure
textCapitalization,keyboardType, andplaceholder.
Material Theming Best Practices
Embrace ThemeData and Material 3
- Use
ColorScheme.fromSeed(): Use this to generate a complete, harmonious color palette for both light and dark modes from a single seed color. - Define Light and Dark Themes: Provide both
themeanddarkThemeto yourMaterialAppto support system brightness settings seamlessly. - Centralize Component Styles: Customize specific component themes (e.g.,
elevatedButtonTheme,cardTheme,appBarTheme) withinThemeDatato ensure consistency. - Dark/Light Mode and Theme Toggle: Implement support for both light and
dark themes using
themeanddarkThemeproperties ofMaterialApp. ThethemeModeproperty can be dynamically controlled (e.g., via aChangeNotifierProvider) to allow for toggling betweenThemeMode.light,ThemeMode.dark, orThemeMode.system.
// main.dart
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
textTheme: const TextTheme(
displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold),
bodyMedium: TextStyle(fontSize: 14.0, height: 1.4),
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
),
home: const MyHomePage(),
);
Implement Design Tokens with ThemeExtension
For custom styles that aren't part of the standard ThemeData, use
ThemeExtension to define reusable design tokens.
- Create a Custom Theme Extension: Define a class that extends
ThemeExtension<T>and include your custom properties. - Implement
copyWithandlerp: These methods are required for the extension to work correctly with theme transitions. - Register in
ThemeData: Add your custom extension to theextensionslist in yourThemeData. - Access Tokens in Widgets: Use
Theme.of(context).extension<MyColors>()!to access your custom tokens.
// 1. Define the extension
@immutable
class MyColors extends ThemeExtension<MyColors> {
const MyColors({required this.success, required this.danger});
final Color? success;
final Color? danger;
@override
ThemeExtension<MyColors> copyWith({Color? success, Color? danger}) {
return MyColors(success: success ?? this.success, danger: danger ?? this.danger);
}
@override
ThemeExtension<MyColors> lerp(ThemeExtension<MyColors>? other, double t) {
if (other is! MyColors) return this;
return MyColors(
success: Color.lerp(success, other.success, t),
danger: Color.lerp(danger, other.danger, t),
);
}
}
// 2. Register it in ThemeData
theme: ThemeData(
extensions: const <ThemeExtension<dynamic>>[
MyColors(success: Colors.green, danger: Colors.red),
],
),
// 3. Use it in a widget
Container(
color: Theme.of(context).extension<MyColors>()!.success,
)
Styling with WidgetStateProperty
WidgetStateProperty.resolveWith: Provide a function that receives aSet<WidgetState>and returns the appropriate value for the current state.WidgetStateProperty.all: A shorthand for when the value is the same for all states.
// Example: Creating a button style that changes color when pressed.
final ButtonStyle myButtonStyle = ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.pressed)) {
return Colors.green; // Color when pressed
}
return Colors.red; // Default color
},
),
);
Layout Best Practices
Building Flexible and Overflow-Safe Layouts
For Rows and Columns
Expanded: Use to make a child widget fill the remaining available space along the main axis.Flexible: Use when you want a widget to shrink to fit, but not necessarily grow. Don't combineFlexibleandExpandedin the sameRoworColumn.Wrap: Use when you have a series of widgets that would overflow aRoworColumn, and you want them to move to the next line.
For General Content
SingleChildScrollView: Use when your content is intrinsically larger than the viewport, but is a fixed size.ListView/GridView: For long lists or grids of content, always use a builder constructor (.builder).FittedBox: Use to scale or fit a single child widget within its parent.LayoutBuilder: Use for complex, responsive layouts to make decisions based on the available space.
Layering Widgets with Stack
Positioned: Use to precisely place a child within aStackby anchoring it to the edges.Align: Use to position a child within aStackusing alignments likeAlignment.center.
Advanced Layout with Overlays
-
OverlayPortal: Use this widget to show UI elements (like custom dropdowns or tooltips) "on top" of everything else. It manages theOverlayEntryfor you.class MyDropdown extends StatefulWidget { const MyDropdown({super.key}); @override State<MyDropdown> createState() => _MyDropdownState(); } class _MyDropdownState extends State<MyDropdown> { final _controller = OverlayPortalController(); @override Widget build(BuildContext context) { return OverlayPortal( controller: _controller, overlayChildBuilder: (BuildContext context) { return const Positioned( top: 50, left: 10, child: Card( child: Padding( padding: EdgeInsets.all(8.0), child: Text('I am an overlay!'), ), ), ); }, child: ElevatedButton( onPressed: _controller.toggle, child: const Text('Toggle Overlay'), ), ); } }