Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
with:
flutter-version: "3.44.0"
# DCM requires separately licensed CI credentials. Keep its standalone
# local check, while GitHub Actions uses Dart Analyzer for static checks.
run-dcm: false

fortal_atlas:
Expand Down
33 changes: 7 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ final style = RemixButtonStyler()
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.animate(AnimationConfig.spring(300.ms))
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700)
);
.onHovered(.color(Colors.blue.shade700));

RemixButton(
onPressed: () {},
Expand All @@ -46,10 +43,7 @@ final button = RemixButtonStyler()
.paddingY(10)
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700)
)
.onHovered(.color(Colors.blue.shade700))
.animate(AnimationConfig.spring(300.ms));

button(
Expand Down Expand Up @@ -125,13 +119,8 @@ final button = RemixButtonStyler()
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.label(TextStyler().color(Colors.white))
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700),
)
.onPressed(
RemixButtonStyler().wrap(.scale(x: 0.95, y: 0.95)),
);
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95));
```

### Adding Animation
Expand All @@ -145,14 +134,8 @@ final style = RemixButtonStyler()
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.animate(AnimationConfig.spring(300.ms))
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700),
)
.onPressed(
RemixButtonStyler()
.wrap(.scale(x: 0.95, y: 0.95)),
);
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95));
```

This example animates both the color on hover and the scale on press, creating a smooth interactive experience for your users.
Expand Down Expand Up @@ -217,9 +200,7 @@ Generated Fortal widgets call the matching `fortal*Styler` internally. Use those
final style = fortalButtonStyler(variant: FortalButtonVariant.solid)
.borderRadiusAll(const Radius.circular(8))
.paddingX(32)
.onHovered(
RemixButtonStyler().wrap(.scale(x: 1.05, y: 1.05)),
);
.onHovered(.scale(1.05));
```

### Fortal Design Tokens
Expand Down
4 changes: 4 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
{
"title": "Getting Started",
"href": "/"
},
{
"title": "Styler API",
"href": "/styler-api"
}
]
},
Expand Down
11 changes: 5 additions & 6 deletions docs/components/button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,14 @@ class ButtonExample extends StatelessWidget {
)
.wrap(.scale(x: 1, y: 1))
.onPressed(
RemixButtonStyler().wrap(.scale(x: 0.90, y: 0.90)),
.scale(0.90),
)
.onHovered(
RemixButtonStyler()
.backgroundColor(const Color(0xFF732D2D))
.color(const Color(0xFF732D2D))
.animate(AnimationConfig.spring(300.ms)),
)
.onFocused(
RemixButtonStyler().backgroundColor(const Color(0xFF732D2D)),
.color(const Color(0xFF732D2D)),
);
}

Expand All @@ -90,10 +89,10 @@ class ButtonExample extends StatelessWidget {
.spreadRadius(0),
)
.onHovered(
RemixButtonStyler().backgroundColor(const Color(0xFF357857)),
.color(const Color(0xFF357857)),
)
.onFocused(
RemixButtonStyler().backgroundColor(const Color(0xFF357857)),
.color(const Color(0xFF357857)),
);
}
}
Expand Down
33 changes: 7 additions & 26 deletions docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ final style = RemixButtonStyler()
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.animate(AnimationConfig.spring(300.ms))
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700)
);
.onHovered(.color(Colors.blue.shade700));

RemixButton(
onPressed: () {},
Expand All @@ -48,10 +45,7 @@ final button = RemixButtonStyler()
.paddingY(10)
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700)
)
.onHovered(.color(Colors.blue.shade700))
.animate(AnimationConfig.spring(300.ms));

button(
Expand Down Expand Up @@ -127,13 +121,8 @@ final style = RemixButtonStyler()
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.label(TextStyler().color(Colors.white))
.onHovered( // [!code ++]
RemixButtonStyler() // [!code ++]
.color(Colors.blue.shade700), // [!code ++]
) // [!code ++]
.onPressed( // [!code ++]
RemixButtonStyler().wrap(.scale(x: 0.95, y: 0.95)), // [!code ++]
); // [!code ++]
.onHovered(.color(Colors.blue.shade700)) // [!code ++]
.onPressed(.scale(0.95)); // [!code ++]
```

### Adding Animation
Expand All @@ -147,14 +136,8 @@ final style = RemixButtonStyler()
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.animate(AnimationConfig.spring(300.ms)) // [!code ++]
.onHovered(
RemixButtonStyler()
.color(Colors.blue.shade700),
)
.onPressed(
RemixButtonStyler()
.wrap(.scale(x: 0.95, y: 0.95)),
);
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95));
```

This example animates both the color on hover and the scale on press, creating a smooth interactive experience for your users.
Expand Down Expand Up @@ -219,9 +202,7 @@ Generated Fortal widgets call the matching `fortal*Styler` internally. Use those
final style = fortalButtonStyler(variant: FortalButtonVariant.solid)
.borderRadiusAll(const Radius.circular(8))
.paddingX(32)
.onHovered(
RemixButtonStyler().wrap(.scale(x: 1.05, y: 1.05)),
);
.onHovered(.scale(1.05));
```

### Fortal Design Tokens
Expand Down
136 changes: 136 additions & 0 deletions docs/styler-api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
title: Styler API
description: Factory constructors, fluent methods, contextual shorthand, and Remix styler conventions
---

Remix stylers expose matching named factories and fluent methods for canonical
style operations. This symmetry lets a state variant use Dart's contextual dot
shorthand without constructing another styler explicitly:

```dart
final style = RemixButtonStyler()
.color(Colors.blue)
.onHovered(.color(Colors.indigo))
.onPressed(.scale(0.97));
```

The following expressions are equivalent:

```dart
RemixCardStyler.color(Colors.blue)
RemixCardStyler().color(Colors.blue)
```

## Factory policy

| API shape | Policy |
| --- | --- |
| Canonical style operation | Expose both a named factory and a matching fluent method. |
| Direct child styler field | Expose a field factory such as `RemixMenuStyler.trigger(...)`. |
| Primary nested container | Forward one compatible canonical surface from the nested styler. |
| Alias or expanded convenience | Keep it fluent-only and use its canonical operation in contextual shorthand. |
| Styler lifecycle or composition (`animate`, `variants`, `wrap`, `modifier`, `merge`) | Keep it fluent-only because it configures or combines an existing parent styler. |
| Generic variant or callable widget helper | Keep it as an extension method; it operates on an existing styler. |
| Name conflict | Give the component-specific behavior a descriptive name and reserve the canonical name for the generated surface. |

Factories are generated from each component spec. Do not add public methods
directly to generated `*.g.dart` files.

### Canonical operations and conveniences

Canonical operations include APIs such as `color`, `padding`, `borderRadius`,
`scale`, and direct child fields such as `label`. Expanded conveniences and
aliases remain fluent-only. For example, use the canonical factory inside a
variant:

```dart
final style = RemixCardStyler()
.paddingAll(12)
.backgroundColor(Colors.white)
.onHovered(.padding(EdgeInsetsGeometryMix.all(16)))
.onPressed(.color(Colors.grey.shade100));
```

Here, `paddingAll` expands to `padding`, and `backgroundColor` aliases `color`.
They do not add duplicate named factories.

Legacy convenience factories map to canonical factories as follows. Their
fluent convenience methods remain available on an existing styler.

| Legacy convenience factory | Canonical contextual replacement |
| --- | --- |
| `backgroundColor(value)` | `.color(value)` |
| `RemixAccordionStyler.titleColor(value)` | `.title(.color(value))` |
| `titleFontSize(value)` / `titleFontWeight(value)` | `.title(.fontSize(value))` / `.title(.fontWeight(value))` |
| `titleStyle(value)` | `.title(.style(value))` |
| `leadingIconColor(value)` / `leadingIconSize(value)` | `.leadingIcon(.color(value))` / `.leadingIcon(.size(value))` |
| `trailingIconColor(value)` / `trailingIconSize(value)` | `.trailingIcon(.color(value))` / `.trailingIcon(.size(value))` |
| `contentColor(value)` / `contentPadding(value)` / `contentDecoration(value)` | `.content(.color(value))` / `.content(.padding(value))` / `.content(.decoration(value))` |
| `RemixAvatarStyler.square(value)` | `.size(value, value)` |
| `RemixAvatarStyler.foregroundColor(value)` | `.label(.color(value)).iconColor(value)` |
| `RemixBadgeStyler.foregroundColor(value)` | `.label(.color(value))` |
| `RemixCalloutStyler.foregroundColor(value)` | `.icon(.color(value)).textColor(value)` |
| `RemixCalloutStyler.iconSize(value)` | `.icon(.size(value))` |
| `RemixCalloutStyler.textStyle(value)` | `.text(.style(value))` |

### Forwarded surfaces

Most visual components forward the canonical surface of their primary `Box`
or `FlexBox` container. A composite root without one clear visual surface, such
as `RemixMenuStyler`, exposes factories for its child fields instead:

```dart
final style = RemixMenuStyler.trigger(
RemixMenuTriggerStyler.color(Colors.black),
);
```

`RemixSelectStyler` stores its popup container as a `FlexBox`, but intentionally
forwards only the compatible `Box` surface. This exposes operations such as
`color`, `padding`, and `scale` without leaking popup layout controls through
the root select styler.

The upstream forwarded `transform` factory currently accepts `Alignment`
rather than the wider `AlignmentGeometry` accepted by the former handwritten
helpers. Use a direct child styler when a directional alignment is required,
for example `RemixCardStyler.container(BoxStyler(transform: matrix,
transformAlignment: AlignmentDirectional.centerStart))`.

### Variants and selected state

Widget-state and selected-state helpers consume the same styler type, so named
factories work contextually for every generated Remix styler:

```dart
final checkboxStyle = RemixCheckboxStyler()
.onHovered(.color(Colors.grey.shade100))
.onSelected(.color(Colors.green));
```

## Resolved naming conflicts

Three legacy helpers used names that now belong to a canonical forwarded
surface. Their component-specific behavior remains available under explicit
names:

| Styler | Canonical generated API | Component-specific helper |
| --- | --- | --- |
| `RemixTextFieldStyler` | `color` styles the container | `textColor` styles editable text |
| `RemixButtonStyler` | `rotate` transforms the container | `modifierRotate` rotates the complete widget with a modifier |
| `RemixCalloutStyler` | `textStyle(TextStyler)` applies the container text style | `contentTextStyle(TextStyleMix)` styles callout content |

This keeps contextual shorthand predictable while preserving each specialized
operation without ambiguous overloads.

## Generation boundary

`Remix*Styler` classes are generated from `@MixableSpec`. Fortal widget wrapper
classes are source-authored even though they were previously emitted by
`@MixWidget`. Both upstream generators write shared parts for the same library;
during a clean build, the widget generator cannot resolve a Styler that the
spec generator has not yet combined into that library.

Only the wrapper shell was materialized—the `fortal*Styler` recipes and widget
behavior remain unchanged. This keeps clean builds deterministic and preserves
the former generated constructors and fields. The widget builder can be
re-enabled once upstream generation supports that dependency ordering.
2 changes: 2 additions & 0 deletions packages/demo/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ environment:

dependency_overrides:
accessibility_tools: ^2.8.0
# Keep Mix and Atlas on their compatible revision while the generator and
# annotation packages use their published beta through Remix.
mix:
git:
url: https://github.com/btwld/mix.git
Expand Down
Loading
Loading