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
38 changes: 37 additions & 1 deletion .agents/skills/mix/references/styler-api-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,50 @@ These are mid-chain or end-of-chain policy choices. Some may also have generated
| Category | Methods |
|---|---|
| Compound spacing | `padding(.all(8))`, `padding(.horizontal(8))`, `padding(.vertical(8))`, `margin(.all(8))`, `margin(.horizontal(8))`, `margin(.vertical(8))`, `padding(.only(left: 8, right: 8))` |
| Compound border | `border(.all())`, `border(.top())`, `borderRadius(.circular())` |
| Compound border | `border(.color(...).width(...))`, `border(.all())`, `border(.top())`, `borderRadius(.circular())` |
| Compound box | `shadow(.color(...).blurRadius(...))`, `backgroundImageUrl` |
| Text directives | `uppercase`, `lowercase`, `capitalize`, `titlecase`, `sentencecase` |
| Variants and local constraints | `onHovered`, `onPressed`, `onDark`, `onLight`, `onDisabled`, `onFocused`, `variant`, `onBreakpoint`, Grid `onConstraints` |
| Advanced modifiers | `wrap`, `phaseAnimation`, `keyframeAnimation` |

**Rationale:** Factory constructors are reserved for primitives that map to stable style concepts. Compound convenience methods remain as instance methods to keep the static API focused.

### Uniform borders skip the side wrapper

`BoxBorderMix` exposes `color`, `width`, `style`, and `strokeAlign` statics that each build an all-sides border — `BoxBorderMix.color(v)` is defined as `BorderMix.all(BorderSideMix.color(v))`. So a border that is the same on every side needs no side wrapper:

```dart
// CORRECT — uniform border, side properties straight off border()
BoxStyler().border(.color(Colors.grey).width(1))

// Redundant — .all() wrapping a side that is then built anyway
BoxStyler().border(.all(.color(Colors.grey).width(1)))
```

Reach for `.all(...)`, `.top(...)`, and friends when the side wrapper earns its place:

```dart
// A specific side
BoxStyler().border(.top(.color(Colors.grey).width(1)))

// Forwarding optional values — the chained setters take non-nullable arguments,
// so a wrapper that passes nullables through needs the constructor
BoxStyler().border(.all(BorderSideMix(color: color, width: width)))

// A prebuilt side reused across several borders
BoxStyler().border(.all(sharedSide))
```

Constants that live on a Flutter class stay qualified, because dot shorthand resolves against the *parameter's* type. `strokeAlign` takes a `double`, so `.strokeAlignOutside` does not resolve:

```dart
// CORRECT
border(.color(c).strokeAlign(BorderSide.strokeAlignOutside))

// WRONG — the context type is double, not BorderSide
border(.color(c).strokeAlign(.strokeAlignOutside))
```

## Composition Decision Tree

- Fixed size? → `BoxStyler().size(w, h)`
Expand Down
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ Flutter developers commonly face these challenges when building custom UIs:

```dart
final style = ButtonStyler()
.paddingX(16)
.paddingY(10)
.padding(.horizontal(16))
.padding(.vertical(10))
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.borderRadius(.all(const Radius.circular(8)))
.animate(AnimationConfig.spring(300.ms))
.onHovered(.color(Colors.blue.shade700));

Expand All @@ -93,10 +93,10 @@ RemixButton(
Or using callable styles:
```dart
final button = ButtonStyler()
.paddingX(16)
.paddingY(10)
.padding(.horizontal(16))
.padding(.vertical(10))
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.borderRadius(.all(const Radius.circular(8)))
.onHovered(.color(Colors.blue.shade700))
.animate(AnimationConfig.spring(300.ms));

Expand Down Expand Up @@ -140,10 +140,10 @@ import 'package:remix/remix.dart';
class MyApp extends StatelessWidget {

final button = ButtonStyler()
.paddingX(16)
.paddingY(10)
.padding(.horizontal(16))
.padding(.vertical(10))
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.borderRadius(.all(const Radius.circular(8)))
.label(TextStyler().color(Colors.white));

@override
Expand All @@ -167,10 +167,10 @@ Easily define how components should look in different interaction states.

```dart
final button = ButtonStyler()
.paddingX(16)
.paddingY(10)
.padding(.horizontal(16))
.padding(.vertical(10))
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.borderRadius(.all(const Radius.circular(8)))
.label(TextStyler().color(Colors.white))
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95));
Expand All @@ -182,10 +182,10 @@ Make your button style smoothly animate when its state changes by chaining `.ani

```dart
final style = ButtonStyler()
.paddingX(16)
.paddingY(10)
.padding(.horizontal(16))
.padding(.vertical(10))
.color(Colors.blue)
.borderRadiusAll(const Radius.circular(8))
.borderRadius(.all(const Radius.circular(8)))
.animate(AnimationConfig.spring(300.ms))
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95));
Expand All @@ -201,9 +201,9 @@ Create base styles and extend them to build variants:

```dart
final baseButtonStyle = ButtonStyler()
.paddingX(16)
.paddingY(10)
.borderRadiusAll(const Radius.circular(8));
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.all(const Radius.circular(8)));

final primaryButton = baseButtonStyle
.color(Colors.blue)
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/lib/pages/charts_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ Widget _badgePie(BuildContext context, List<Color> palette) {
.size(26, 26)
.alignment(.center)
.color(panel)
.borderAll(color: border)
.borderRounded(99),
.border(.color(border))
.borderRadius(.circular(99)),
child: StyledIcon(
icon: icons[index],
style: IconStyler().size(14).color(iconColor),
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/lib/shell/sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class _Brand extends StatelessWidget {
Widget build(BuildContext context) {
return Box(
key: const ValueKey('dashboard-brand'),
style: BoxStyler().paddingAll(FortalTokens.space4()),
style: BoxStyler().padding(.all(FortalTokens.space4())),
child: const DashboardTextTone(
child: FortalText('Dashboard', size: .size5, weight: .bold),
),
Expand Down
16 changes: 11 additions & 5 deletions apps/dashboard/lib/widgets/chart_legend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ class _LegendMark extends StatelessWidget {
return switch (item.pattern) {
.solid => Box(
key: key,
style: BoxStyler().size(18, 4).color(item.color).borderRounded(radius),
style: BoxStyler()
.size(18, 4)
.color(item.color)
.borderRadius(.circular(radius)),
),
.dashed => RowBox(
key: key,
Expand All @@ -115,20 +118,23 @@ class _LegendMark extends StatelessWidget {
style: BoxStyler()
.size(7, 4)
.color(item.color)
.borderRounded(radius),
.borderRadius(.circular(radius)),
),
],
),
.outlined => Box(
key: key,
style: BoxStyler()
.size(14, 14)
.borderAll(color: item.color, width: 2)
.borderRounded(radius),
.border(.color(item.color).width(2))
.borderRadius(.circular(radius)),
),
.dot => Box(
key: key,
style: BoxStyler().size(10, 10).color(item.color).borderRounded(99),
style: BoxStyler()
.size(10, 10)
.color(item.color)
.borderRadius(.circular(99)),
),
};
}
Expand Down
16 changes: 8 additions & 8 deletions apps/dashboard/lib/widgets/theme_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,24 +188,24 @@ class _AccentSwatch extends StatelessWidget {
required this.onPressed,
});

static final _ring = ToggleStyler().borderAll(
color: FortalTokens.focus8(),
width: FortalTokens.focusRingWidth(),
strokeAlign: BorderSide.strokeAlignOutside,
static final _ring = ToggleStyler().border(
.color(FortalTokens.focus8())
.width(FortalTokens.focusRingWidth())
.strokeAlign(BorderSide.strokeAlignOutside),
);

static final _style =
ToggleStyler(
container: FlexBoxStyler()
.size(30, 30)
.alignment(.center)
.borderRadiusAll(const Radius.circular(15)),
.borderRadius(.all(const Radius.circular(15))),
icon: .size(15),
)
.backgroundColor(FortalTokens.accent9())
.color(FortalTokens.accent9())
.iconColor(Colors.transparent)
.onHovered(ToggleStyler().backgroundColor(FortalTokens.accent10()))
.onPressed(ToggleStyler().backgroundColor(FortalTokens.accent10()))
.onHovered(ToggleStyler().color(FortalTokens.accent10()))
.onPressed(ToggleStyler().color(FortalTokens.accent10()))
.onSelected(
ToggleStyler()
.iconColor(FortalTokens.accentContrast())
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/test/app_smoke_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ void main() {
await tester.tap(find.byKey(const ValueKey('nav-customers')).first);
await tester.pump();

// The generated Fortal wrapper forwards the key to the Remix widget it
// builds, so the key matches both.
// FortalDataTable forwards the key to the Remix widget it builds, so the
// key matches both.
expect(find.byKey(const ValueKey('data-grid-customers')), findsWidgets);
expect(find.text('1–10 of 24'), findsOneWidget);
});
Expand Down
52 changes: 28 additions & 24 deletions apps/playground/lib/registry/entries/button_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,58 @@ Widget buildButtonExample() {
label: 'Primary Button',
onPressed: () {},
style: ButtonStyler()
.paddingX(16)
.paddingY(10)
.borderRadiusAll(const Radius.circular(8))
.backgroundColor(const Color(0xFF1F2937))
.foregroundColor(Colors.white),
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.all(const Radius.circular(8)))
.color(const Color(0xFF1F2937))
.label(.color(Colors.white))
.icon(.color(Colors.white)),
),
RemixButton(
label: 'Disabled',
onPressed: null,
style: ButtonStyler()
.paddingX(16)
.paddingY(10)
.borderRadiusAll(const Radius.circular(8))
.backgroundColor(const Color(0xFFE5E7EB))
.foregroundColor(const Color(0xFF9CA3AF)),
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.all(const Radius.circular(8)))
.color(const Color(0xFFE5E7EB))
.label(.color(const Color(0xFF9CA3AF)))
.icon(.color(const Color(0xFF9CA3AF))),
),
RemixButton(
label: 'Loading',
loading: true,
onPressed: () {},
style: ButtonStyler()
.paddingX(16)
.paddingY(10)
.borderRadiusAll(const Radius.circular(8))
.backgroundColor(const Color(0xFF1F2937).withValues(alpha: 0.6))
.foregroundColor(Colors.white.withValues(alpha: 0.7))
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.all(const Radius.circular(8)))
.color(const Color(0xFF1F2937).withValues(alpha: 0.6))
.label(.color(Colors.white.withValues(alpha: 0.7)))
.icon(.color(Colors.white.withValues(alpha: 0.7)))
.spinner(.indicatorColor(Colors.white)),
),
RemixButton(
label: 'With Icon',
leadingIcon: Icons.star,
onPressed: () {},
style: ButtonStyler()
.paddingX(16)
.paddingY(10)
.borderRadiusAll(const Radius.circular(8))
.backgroundColor(const Color(0xFF1F2937))
.foregroundColor(Colors.white),
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.all(const Radius.circular(8)))
.color(const Color(0xFF1F2937))
.label(.color(Colors.white))
.icon(.color(Colors.white)),
),
RemixIconButton(
icon: Icons.star,
onPressed: () {},
semanticLabel: 'Favorite',
style: IconButtonStyler()
.paddingAll(10)
.borderRadiusAll(const Radius.circular(8))
.backgroundColor(const Color(0xFF1F2937))
.foregroundColor(Colors.white),
.padding(.all(10))
.borderRadius(.all(const Radius.circular(8)))
.color(const Color(0xFF1F2937))
.iconColor(Colors.white),
),
],
material: [
Expand Down
12 changes: 8 additions & 4 deletions apps/playground/lib/registry/entries/skeleton_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ class _SkeletonPreviewState extends State<_SkeletonPreview> {
crossAxisAlignment: .start,
children: [
RemixSkeleton(
style: base.container(BoxStyler().size(40, 40).shapeCircle()),
style: base.container(
BoxStyler().size(40, 40).shape(.circle()),
),
),
SpacedColumn(
spacing: 8,
Expand All @@ -76,20 +78,22 @@ class _SkeletonPreviewState extends State<_SkeletonPreview> {
children: [
RemixSkeleton(
style: base.container(
BoxStyler().size(180, 12).borderRounded(6),
BoxStyler().size(180, 12).borderRadius(.circular(6)),
),
),
RemixSkeleton(
style: base.container(
BoxStyler().size(120, 12).borderRounded(6),
BoxStyler().size(120, 12).borderRadius(.circular(6)),
),
),
],
),
],
),
RemixSkeleton(
style: base.container(BoxStyler().size(320, 88).borderRounded(8)),
style: base.container(
BoxStyler().size(320, 88).borderRadius(.circular(8)),
),
),
],
),
Expand Down
6 changes: 3 additions & 3 deletions apps/playground/lib/registry/entries/textarea_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ class _TextAreaExampleState extends State<_TextAreaExample> {
final muted = isDark ? const Color(0xFFB8BDC7) : const Color(0xFF667085);

final customStyle = TextFieldStyler()
.backgroundColor(surface)
.color(surface)
.textColor(foreground)
.hintColor(muted)
.paddingAll(14)
.borderRadiusAll(const Radius.circular(12))
.padding(.all(14))
.borderRadius(.all(const Radius.circular(12)))
.border(
BoxBorderMix.all(
BorderSideMix(color: const Color(0xFF7C3AED), width: 1.5),
Expand Down
8 changes: 4 additions & 4 deletions docs/components/accordion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class _AccordionExampleState extends State<AccordionExample> {
BorderSideMix().color(Colors.grey.shade300).width(1),
),
)
.borderRounded(8)
.borderRadius(.circular(8))
.clipBehavior(Clip.antiAlias),
)
// Trigger and content own only their own internal layout.
Expand All @@ -123,10 +123,10 @@ class _AccordionExampleState extends State<AccordionExample> {
.direction(Axis.horizontal)
.mainAxisAlignment(MainAxisAlignment.spaceBetween)
.spacing(12)
.paddingX(16)
.paddingY(14),
.padding(.horizontal(16))
.padding(.vertical(14)),
)
.content(BoxStyler().paddingX(16).paddingBottom(14))
.content(BoxStyler().padding(.horizontal(16)).padding(.bottom(14)))
// A widget-state variant tracks the trigger, and can style any part.
.onHovered(
AccordionStyler().container(BoxStyler().color(Colors.grey.shade100)),
Expand Down
Loading
Loading