From 82984e91351720d41404ac70b494b168ef641193 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62367544+tilucasoli@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:46:06 -0300 Subject: [PATCH 1/2] docs: codegen for Mix specs and stylers Update the 'creating-a-widget' tutorial to demonstrate using mix_annotations code generation. The doc now shows @MixableSpec and @MixableStyler usage, explains @MixableField(setterType: ...) for public setter types, and includes sample generated output (button_spec.g.dart and button_style.g.dart). Also adds instructions to run build_runner and updates the summary/table describing what the developer writes vs. what the generator provides. --- .../tutorials/creating-a-widget.mdx | 189 +++++++++++++----- 1 file changed, 143 insertions(+), 46 deletions(-) diff --git a/website/src/content/documentation/tutorials/creating-a-widget.mdx b/website/src/content/documentation/tutorials/creating-a-widget.mdx index 823ab913a6..f9aaab7188 100644 --- a/website/src/content/documentation/tutorials/creating-a-widget.mdx +++ b/website/src/content/documentation/tutorials/creating-a-widget.mdx @@ -1,7 +1,7 @@ # Building a Design System Widget -This guide walks through creating a design system button with Mix, demonstrating Specs, Stylers, variants, and state handling. +This guide walks through creating a design system button with Mix, demonstrating Specs, Stylers, variants, and state handling — using **annotations and code generation** to eliminate boilerplate. ![Button Example](./images/button-example.png) @@ -33,18 +33,49 @@ This guide walks through creating a design system button with Mix, demonstrating ### Create a Button Spec -A `Spec` defines resolved visual properties. `ButtonSpec` contains specs for container, icon, and label: +A `Spec` defines resolved visual properties. With `@MixableSpec()`, the generator creates `copyWith()`, `lerp()`, `debugFillProperties()`, and `props` for you: ```dart +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; +import 'package:mix_annotations/mix_annotations.dart'; -class ButtonSpec extends Spec { +part 'button_spec.g.dart'; + +@MixableSpec() +@immutable +final class ButtonSpec extends Spec + with Diagnosticable, _$ButtonSpecMethods { + @override final StyleSpec? container; + @override final StyleSpec? icon; + @override final StyleSpec? label; const ButtonSpec({this.container, this.icon, this.label}); +} +``` + +That's it — no manual `copyWith`, `lerp`, or `props`. The generated mixin `_$ButtonSpecMethods` provides all of those. Run code generation with: + +```bash +dart run build_runner build +``` + +
+Generated code (`button_spec.g.dart`) + +```dart +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'button_spec.dart'; + +mixin _$ButtonSpecMethods on Spec, Diagnosticable { + StyleSpec? get container; + StyleSpec? get icon; + StyleSpec? get label; @override ButtonSpec copyWith({ @@ -60,7 +91,7 @@ class ButtonSpec extends Spec { } @override - ButtonSpec lerp(covariant ButtonSpec? other, double t) { + ButtonSpec lerp(ButtonSpec? other, double t) { return ButtonSpec( container: container?.lerp(other?.container, t), icon: icon?.lerp(other?.icon, t), @@ -68,21 +99,42 @@ class ButtonSpec extends Spec { ); } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('container', container)) + ..add(DiagnosticsProperty('icon', icon)) + ..add(DiagnosticsProperty('label', label)); + } + @override List get props => [container, icon, label]; } ``` +
+ ### Create a Button Styler -`ButtonStyler` provides a fluent interface for styling. It extends `Style` and uses `WidgetStateVariantMixin` for state support: +`ButtonStyler` provides a fluent interface for styling. With `@MixableStyler()`, the generator creates setters, `merge()`, `resolve()`, `debugFillProperties()`, and `props`. + +Use `@MixableField(setterType: ...)` to tell the generator the public type for each setter. Since the fields are `Prop>` internally but users pass Styler types, `setterType` bridges that gap: ```dart -class ButtonStyler extends Style - with VariantStyleMixin, - WidgetStateVariantMixin { +part 'button_style.g.dart'; + +@MixableStyler() +class ButtonStyler extends MixStyler + with _$ButtonStylerMixin { + @override + @MixableField(setterType: FlexBoxStyler) final Prop>? $container; + @override + @MixableField(setterType: IconStyler) final Prop>? $icon; + @override + @MixableField(setterType: TextStyler) final Prop>? $label; ButtonStyler({ @@ -96,20 +148,18 @@ class ButtonStyler extends Style $icon = Prop.maybeMix(icon), $label = Prop.maybeMix(label); - // Component methods - ButtonStyler container(FlexBoxStyler value) { - return merge(ButtonStyler(container: value)); - } - - ButtonStyler icon(IconStyler value) { - return merge(ButtonStyler(icon: value)); - } - - ButtonStyler label(TextStyler value) { - return merge(ButtonStyler(label: value)); - } + const ButtonStyler.create({ + Prop>? container, + Prop>? icon, + Prop>? label, + super.animation, + super.modifier, + super.variants, + }) : $container = container, + $icon = icon, + $label = label; - // Convenience methods + // Convenience methods (beyond the generated container/icon/label setters) ButtonStyler backgroundColor(Color value) { return merge(ButtonStyler(container: FlexBoxStyler().color(value))); } @@ -132,50 +182,88 @@ class ButtonStyler extends Style ); } - ButtonStyler.create({ - Prop>? container, - Prop>? icon, - Prop>? label, - super.animation, - super.modifier, - super.variants, - }) : $container = container, - $icon = icon, - $label = label; + ButtonStyler scale(double value) { + return merge(ButtonStyler(container: FlexBoxStyler().scale(value))); + } +} +``` + +The `@MixableField(setterType: FlexBoxStyler)` annotation tells the generator to produce `ButtonStyler container(FlexBoxStyler value)` instead of `ButtonStyler container(StyleSpec value)`. This gives users the fluent Styler API they expect. + +`MixStyler` already provides `WidgetStateVariantMixin` (for `onPressed`, `onHovered`, `onDisabled`, etc.), `VariantStyleMixin`, and `AnimationStyleMixin` — so you get state handling for free. + +The generated `_$ButtonStylerMixin` handles setters, `merge()`, `resolve()`, `debugFillProperties()`, and `props`: + +
+Generated code (`button_style.g.dart`) + +```dart +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'button_style.dart'; + +mixin _$ButtonStylerMixin on Style, Diagnosticable { + Prop>? get $container; + Prop>? get $icon; + Prop>? get $label; + + // Generated setters using @MixableField(setterType:) + ButtonStyler container(FlexBoxStyler value) { + return merge(ButtonStyler(container: value)); + } + + ButtonStyler icon(IconStyler value) { + return merge(ButtonStyler(icon: value)); + } + + ButtonStyler label(TextStyler value) { + return merge(ButtonStyler(label: value)); + } @override - ButtonStyler merge(covariant ButtonStyler? other) { + ButtonStyler merge(ButtonStyler? other) { return ButtonStyler.create( container: MixOps.merge($container, other?.$container), icon: MixOps.merge($icon, other?.$icon), label: MixOps.merge($label, other?.$label), - animation: MixOps.mergeAnimation($animation, other?.$animation), - modifier: MixOps.mergeModifier($modifier, other?.$modifier), variants: MixOps.mergeVariants($variants, other?.$variants), + modifier: MixOps.mergeModifier($modifier, other?.$modifier), + animation: MixOps.mergeAnimation($animation, other?.$animation), ); } - @override - List get props => [$container, $icon, $label]; - @override StyleSpec resolve(BuildContext context) { - final container = MixOps.resolve(context, $container); - final icon = MixOps.resolve(context, $icon); - final label = MixOps.resolve(context, $label); + final spec = ButtonSpec( + container: MixOps.resolve(context, $container), + icon: MixOps.resolve(context, $icon), + label: MixOps.resolve(context, $label), + ); return StyleSpec( - spec: ButtonSpec(container: container, icon: icon, label: label), + spec: spec, + animation: $animation, + widgetModifiers: $modifier?.resolve(context), ); } @override - ButtonStyler variant(Variant variant, ButtonStyler style) { - return merge(ButtonStyler(variants: [VariantStyle(variant, style)])); + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('container', $container)) + ..add(DiagnosticsProperty('icon', $icon)) + ..add(DiagnosticsProperty('label', $label)); } + + @override + List get props => + [$container, $icon, $label, $animation, $modifier, $variants]; } ``` +
+ ### Define Variants Use an enum to define button variants with their styles: @@ -457,9 +545,18 @@ class ButtonExampleScreen extends StatelessWidget { This tutorial covered: -- **ButtonSpec**: Resolved visual properties with animation support -- **ButtonStyler**: Fluent API with state handling via `WidgetStateVariantMixin` +- **`@MixableSpec`**: Generates `copyWith()`, `lerp()`, `debugFillProperties()`, and `props` — no manual boilerplate +- **`@MixableStyler`**: Generates setters, `merge()`, `resolve()`, `debugFillProperties()`, and `props` — you only write convenience methods +- **`@MixableField(setterType:)`**: Controls the public type of generated setters (e.g., accept `FlexBoxStyler` instead of `StyleSpec`) +- **`MixStyler`**: Base class that provides `WidgetStateVariantMixin`, `VariantStyleMixin`, and `AnimationStyleMixin` for free - **ButtonVariant**: Enum associating variants with styles - **CustomButton**: Widget combining `Pressable` and `StyleBuilder` -This pattern extends to other components: cards, inputs, dialogs, etc. +### What you write vs. what's generated + +| You write | Generator provides | +|---|---| +| Spec fields and constructor | `copyWith()`, `lerp()`, `debugFillProperties()`, `props` | +| Styler fields, constructors, convenience methods | Setters (via `@MixableField`), `merge()`, `resolve()`, `debugFillProperties()`, `props` | + +This pattern extends to other components: cards, inputs, dialogs, etc. Add `mix_annotations` to your `dependencies` and `mix_generator` to `dev_dependencies`, then run `dart run build_runner build` to generate code. From 90e95da912c1f4c0c60bb1c6acd68e95f22cbb1e Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Fri, 6 Feb 2026 11:22:11 -0500 Subject: [PATCH 2/2] docs: trim generated code to signatures and fix dependency list - Replace full generated code blocks with key signatures to reduce drift risk as the generator evolves - Add missing animate/variants/wrap methods to styler generated output - Add build_runner to the dependency instructions --- .../tutorials/creating-a-widget.mdx | 94 ++++--------------- 1 file changed, 17 insertions(+), 77 deletions(-) diff --git a/website/src/content/documentation/tutorials/creating-a-widget.mdx b/website/src/content/documentation/tutorials/creating-a-widget.mdx index f9aaab7188..157758137c 100644 --- a/website/src/content/documentation/tutorials/creating-a-widget.mdx +++ b/website/src/content/documentation/tutorials/creating-a-widget.mdx @@ -65,7 +65,7 @@ dart run build_runner build ```
-Generated code (`button_spec.g.dart`) +Generated code (`button_spec.g.dart`) — key signatures ```dart // GENERATED CODE - DO NOT MODIFY BY HAND @@ -73,40 +73,14 @@ dart run build_runner build part of 'button_spec.dart'; mixin _$ButtonSpecMethods on Spec, Diagnosticable { - StyleSpec? get container; - StyleSpec? get icon; - StyleSpec? get label; - @override - ButtonSpec copyWith({ - StyleSpec? container, - StyleSpec? icon, - StyleSpec? label, - }) { - return ButtonSpec( - container: container ?? this.container, - icon: icon ?? this.icon, - label: label ?? this.label, - ); - } + ButtonSpec copyWith({StyleSpec? container, ...}) { ... } @override - ButtonSpec lerp(ButtonSpec? other, double t) { - return ButtonSpec( - container: container?.lerp(other?.container, t), - icon: icon?.lerp(other?.icon, t), - label: label?.lerp(other?.label, t), - ); - } + ButtonSpec lerp(ButtonSpec? other, double t) { ... } @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('container', container)) - ..add(DiagnosticsProperty('icon', icon)) - ..add(DiagnosticsProperty('label', label)); - } + void debugFillProperties(DiagnosticPropertiesBuilder properties) { ... } @override List get props => [container, icon, label]; @@ -195,7 +169,7 @@ The `@MixableField(setterType: FlexBoxStyler)` annotation tells the generator to The generated `_$ButtonStylerMixin` handles setters, `merge()`, `resolve()`, `debugFillProperties()`, and `props`:
-Generated code (`button_style.g.dart`) +Generated code (`button_style.g.dart`) — key signatures ```dart // GENERATED CODE - DO NOT MODIFY BY HAND @@ -203,58 +177,24 @@ The generated `_$ButtonStylerMixin` handles setters, `merge()`, `resolve()`, `de part of 'button_style.dart'; mixin _$ButtonStylerMixin on Style, Diagnosticable { - Prop>? get $container; - Prop>? get $icon; - Prop>? get $label; - - // Generated setters using @MixableField(setterType:) - ButtonStyler container(FlexBoxStyler value) { - return merge(ButtonStyler(container: value)); - } - - ButtonStyler icon(IconStyler value) { - return merge(ButtonStyler(icon: value)); - } + // Setters generated from @MixableField(setterType:) + ButtonStyler container(FlexBoxStyler value) { ... } + ButtonStyler icon(IconStyler value) { ... } + ButtonStyler label(TextStyler value) { ... } - ButtonStyler label(TextStyler value) { - return merge(ButtonStyler(label: value)); - } + // Base methods from MixStyler + ButtonStyler animate(AnimationConfig value) { ... } + ButtonStyler variants(List> value) { ... } + ButtonStyler wrap(WidgetModifierConfig value) { ... } @override - ButtonStyler merge(ButtonStyler? other) { - return ButtonStyler.create( - container: MixOps.merge($container, other?.$container), - icon: MixOps.merge($icon, other?.$icon), - label: MixOps.merge($label, other?.$label), - variants: MixOps.mergeVariants($variants, other?.$variants), - modifier: MixOps.mergeModifier($modifier, other?.$modifier), - animation: MixOps.mergeAnimation($animation, other?.$animation), - ); - } + ButtonStyler merge(ButtonStyler? other) { ... } @override - StyleSpec resolve(BuildContext context) { - final spec = ButtonSpec( - container: MixOps.resolve(context, $container), - icon: MixOps.resolve(context, $icon), - label: MixOps.resolve(context, $label), - ); - - return StyleSpec( - spec: spec, - animation: $animation, - widgetModifiers: $modifier?.resolve(context), - ); - } + StyleSpec resolve(BuildContext context) { ... } @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('container', $container)) - ..add(DiagnosticsProperty('icon', $icon)) - ..add(DiagnosticsProperty('label', $label)); - } + void debugFillProperties(DiagnosticPropertiesBuilder properties) { ... } @override List get props => @@ -559,4 +499,4 @@ This tutorial covered: | Spec fields and constructor | `copyWith()`, `lerp()`, `debugFillProperties()`, `props` | | Styler fields, constructors, convenience methods | Setters (via `@MixableField`), `merge()`, `resolve()`, `debugFillProperties()`, `props` | -This pattern extends to other components: cards, inputs, dialogs, etc. Add `mix_annotations` to your `dependencies` and `mix_generator` to `dev_dependencies`, then run `dart run build_runner build` to generate code. +This pattern extends to other components: cards, inputs, dialogs, etc. Add `mix_annotations` to your `dependencies` and `mix_generator` + `build_runner` to `dev_dependencies`, then run `dart run build_runner build` to generate code.