Skip to content

Commit 3203bb8

Browse files
committed
codelessly_sdk 7.1.0: render badge, chip and segmented button nodes
Port the badge, chip and segmented button transformers from the unmerged migrate-json-serializable branch onto SDK 7 and codelessly_api 5.1.0, and register them with PassiveNodeTransformerManager. - Badge: Material 3 Badge as a dot, label or count (capped at maxCount), wrapping the node named by childID when it exists; label and visibility can be bound; click reactions handled by the manager. - Chip: Chip, ActionChip, ChoiceChip, FilterChip or InputChip; presses fire click, selection updates `selected` and fires changed and click. - SegmentedButton<String>: selection updates `selected` (the branch read and wrote different properties) and fires changed; selections Flutter would assert on are sanitized. Previews keep local state. Add smoke tests for every badge/chip type and segmented button configuration, including reactions firing on selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A67XoQYsLVvoMtiKcXXUxY
1 parent e18c963 commit 3203bb8

9 files changed

Lines changed: 1011 additions & 2 deletions

‎CHANGELOG.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## 7.1.0
2+
3+
- Require codelessly_api 5.1.0.
4+
- Render the new badge, chip and segmented button nodes:
5+
- `PassiveBadgeTransformer` builds a Material 3 `Badge` as a dot, label
6+
or count (capped at `maxCount` like `Badge.count`), around the node
7+
named by `childID` when it exists. `label` and `isLabelVisible` can be
8+
bound to variables, and click reactions work like on other nodes.
9+
- `PassiveChipTransformer` builds a `Chip`, `ActionChip`, `ChoiceChip`,
10+
`FilterChip` or `InputChip`. Presses and delete-icon presses fire click
11+
reactions; selecting a choice or filter chip updates its `selected`
12+
value and fires changed (and click) reactions with the new value.
13+
- `PassiveSegmentedButtonTransformer` builds a `SegmentedButton<String>`.
14+
Selecting segments updates its `selected` value and fires changed
15+
reactions with the list of selected values. Selections it cannot show
16+
(unknown values, several values without multi-selection) are dropped
17+
instead of failing Flutter's assertions.
18+
- Previews keep chip and segmented button selection as local state.
19+
120
## 7.0.1
221

322
- Fix published layouts failing with "Node with id not found" on web

‎lib/src/transformers/node_transformers/node_transformers.dart‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export 'passive_accordion_transformer.dart';
22
export 'passive_app_bar_transformer.dart';
3+
export 'passive_badge_transformer.dart';
34
export 'passive_button_transformer.dart';
45
export 'passive_canvas_transformer.dart';
56
export 'passive_checkbox_transformer.dart';
7+
export 'passive_chip_transformer.dart';
68
export 'passive_divider_transformer.dart';
79
export 'passive_dropdown_transformer.dart';
810
export 'passive_embedded_video_transformer.dart';
@@ -21,6 +23,7 @@ export 'passive_progressbar_transformer.dart';
2123
export 'passive_radio_transformer.dart';
2224
export 'passive_rectangle_transformer.dart';
2325
export 'passive_row_column_transformer.dart';
26+
export 'passive_segmented_button_transformer.dart';
2427
export 'passive_single_placeholder_transformer.dart';
2528
export 'passive_slider_transformer.dart';
2629
export 'passive_spacer_transformer.dart';
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import 'package:codelessly_api/codelessly_api.dart';
2+
import 'package:flutter/material.dart';
3+
4+
import '../../../codelessly_sdk.dart';
5+
6+
class PassiveBadgeTransformer extends NodeWidgetTransformer<BadgeNode> {
7+
PassiveBadgeTransformer(super.getNode, super.manager);
8+
9+
@override
10+
Widget buildWidget(
11+
BadgeNode node,
12+
BuildContext context,
13+
WidgetBuildSettings settings,
14+
) {
15+
return PassiveBadgeWidget(node: node, settings: settings, manager: manager);
16+
}
17+
}
18+
19+
class PassiveBadgeWidget extends StatelessWidget {
20+
final BadgeNode node;
21+
final WidgetBuildSettings settings;
22+
final WidgetNodeTransformerManager manager;
23+
final List<VariableData> variablesOverrides;
24+
25+
const PassiveBadgeWidget({
26+
super.key,
27+
required this.node,
28+
required this.settings,
29+
required this.manager,
30+
this.variablesOverrides = const [],
31+
});
32+
33+
/// The node the badge is anchored to, if it exists.
34+
BaseNode? get childNode {
35+
final String? childID = node.childID;
36+
if (childID == null || childID.isEmpty || childID == node.id) return null;
37+
try {
38+
return manager.getNode(childID);
39+
} on Object {
40+
// A dangling child ID renders the badge on its own.
41+
return null;
42+
}
43+
}
44+
45+
@override
46+
Widget build(BuildContext context) {
47+
final BadgeProperties properties = node.properties;
48+
final ScopedValues scopedValues = ScopedValues.of(
49+
context,
50+
variablesOverrides: variablesOverrides,
51+
);
52+
53+
final String label = properties.effectiveLabel(
54+
PropertyValueDelegate.getPropertyValue<String>(
55+
node,
56+
'label',
57+
scopedValues: scopedValues,
58+
) ??
59+
PropertyValueDelegate.substituteVariables(
60+
properties.label,
61+
scopedValues: scopedValues,
62+
nullSubstitutionMode: settings.nullSubstitutionMode,
63+
),
64+
);
65+
final bool isLabelVisible =
66+
PropertyValueDelegate.getPropertyValue<bool>(
67+
node,
68+
'isLabelVisible',
69+
scopedValues: scopedValues,
70+
) ??
71+
properties.isLabelVisible;
72+
73+
final TextStyle textStyle = TextUtils.retrieveTextStyleFromProp(
74+
properties.textStyle,
75+
);
76+
final Alignment alignment = switch (properties.alignment) {
77+
BadgeAlignment.topRight => Alignment.topRight,
78+
BadgeAlignment.topLeft => Alignment.topLeft,
79+
BadgeAlignment.bottomRight => Alignment.bottomRight,
80+
BadgeAlignment.bottomLeft => Alignment.bottomLeft,
81+
};
82+
83+
final BaseNode? childNode = this.childNode;
84+
final bool isDot = properties.badgeType == BadgeType.dot;
85+
86+
final Widget badge = Badge(
87+
key: ValueKey(
88+
'${node.id}-${IndexedItemProvider.maybeOf(context)?.index ?? ''}',
89+
),
90+
isLabelVisible: isLabelVisible,
91+
label: isDot ? null : Text(label),
92+
backgroundColor: properties.backgroundColor?.toFlutterColor(),
93+
// Flutter applies textColor over the text style's color, so a color
94+
// set on the style is used when no text color is given.
95+
textColor: properties.textColor?.toFlutterColor() ?? textStyle.color,
96+
textStyle: textStyle,
97+
smallSize: properties.smallSize,
98+
largeSize: properties.largeSize,
99+
padding: properties.padding.flutterEdgeInsets,
100+
alignment: alignment,
101+
offset: Offset(properties.offsetX, properties.offsetY),
102+
child: childNode == null
103+
? null
104+
: manager.buildWidgetFromNode(childNode, context, settings: settings),
105+
);
106+
107+
return AdaptiveNodeBox(
108+
node: node,
109+
// Without a child, the badge keeps its own size and sits at its
110+
// alignment inside the node's box.
111+
child: childNode == null
112+
? Align(alignment: alignment, child: badge)
113+
: badge,
114+
);
115+
}
116+
}

0 commit comments

Comments
 (0)