mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 22:47:01 +01:00
Renames this repo's markdown onto README.md / CLAUDE*.md / DOCS*.md / TODO*.md so a future session can tell a live task from a finished record without reading the file to find out. TODO files carry the literal "REMOVE ME AFTER FINISH" and are deleted when the work lands. Enforced by the md-naming pre-commit hook and CI workflow, both delegating to the shared gate in kuhyx/utils so this repo's idea of a valid name cannot drift from every other repo's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A47aX5DJwmA3QFn3pkEWi7
198 lines
6.4 KiB
Markdown
198 lines
6.4 KiB
Markdown
# Flutter AI rules — UI: theming, layout and overlays
|
|
|
|
Material theming, WidgetStateProperty, layout, and overlays.
|
|
|
|
_Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/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 `LayoutBuilder` or `MediaQuery` to create responsive
|
|
UIs.
|
|
* **Text:** Use `Theme.of(context).textTheme` for text styles.
|
|
* **Text Fields:** Configure `textCapitalization`, `keyboardType`, and
|
|
`placeholder`.
|
|
|
|
### 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 `theme` and `darkTheme` to your
|
|
`MaterialApp` to support system brightness settings seamlessly.
|
|
* **Centralize Component Styles:** Customize specific component themes (e.g.,
|
|
`elevatedButtonTheme`, `cardTheme`, `appBarTheme`) within `ThemeData` to
|
|
ensure consistency.
|
|
* **Dark/Light Mode and Theme Toggle:** Implement support for both light and
|
|
dark themes using `theme` and `darkTheme` properties of `MaterialApp`. The
|
|
`themeMode` property can be dynamically controlled (e.g., via a
|
|
`ChangeNotifierProvider`) to allow for toggling between `ThemeMode.light`,
|
|
`ThemeMode.dark`, or `ThemeMode.system`.
|
|
|
|
```dart
|
|
// 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 `copyWith` and `lerp`:** These methods are required for the
|
|
extension to work correctly with theme transitions.
|
|
* **Register in `ThemeData`:** Add your custom extension to the `extensions`
|
|
list in your `ThemeData`.
|
|
* **Access Tokens in Widgets:** Use `Theme.of(context).extension<MyColors>()!`
|
|
to access your custom tokens.
|
|
|
|
```dart
|
|
// 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 a
|
|
`Set<WidgetState>` and returns the appropriate value for the current state.
|
|
* **`WidgetStateProperty.all`:** A shorthand for when the value is the same for
|
|
all states.
|
|
|
|
```dart
|
|
// 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 combine `Flexible` and `Expanded` in the same `Row` or
|
|
`Column`.
|
|
* **`Wrap`:** Use when you have a series of widgets that would overflow a `Row`
|
|
or `Column`, 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 a `Stack` by anchoring it to the edges.
|
|
* **`Align`:** Use to position a child within a `Stack` using alignments like `Alignment.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 the
|
|
`OverlayEntry` for you.
|
|
|
|
```dart
|
|
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'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Color Scheme Best Practices
|